Firefox のアドオンが利用不可能になった先日の事件の技術的詳細

Firefox のほとんどのアドオンが動かなくなるという事件が先日起こりました。原因はアドオンの署名に使っていた証明書の有効期限切れであり、私たちのミスです。現在ではほとんどの人のアドオンが復活し問題が解決されたので、この記事では何が起こったのか、なぜ起こったのか、私たちがどのように修正したかについて詳細に説明します。

背景: アドオンと署名

Firefox をそのまま使っている人も大勢いますが、Firefox には "アドオン" と呼ばれる強力な拡張メカニズムがあります。アドオンを使うとサードパーティの機能を Firefox に追加してデフォルトの機能を拡張できます。現在 15,000 個以上の Firefox アドオンが利用可能であり、例えば広告をブロックするアドオン数百個のタブを管理するアドオンなどがあります。

Firefox はインストールされる全てのアドオンに対してデジタル署名を要求します。この署名は悪意あるアドオンからユーザーを守るために存在し、Mozilla スタッフによる簡単なレビューに合格したことを表します。署名を導入した 2015 年当時、悪意あるアドオンは深刻な問題となっていました。

アドオン署名の仕組みは、インストールされる全ての Firefox に "ルート証明書" を組み込んでおくというものです。ルート証明書はハードウェアセキュリティモジュール (HSM)でオフラインに保管され、数年おきに "中間証明書" に署名を行うために使用されます。この中間証明書はオンラインにされ、実際の署名プロセスで使われます。アドオンが署名を求めると、私たちはまず中間証明書を使って一時的な "エンドエンティティ証明書" を新たに発行し、このエンドエンティティ証明書を使ってアドオンを署名します。この流れを図示すると以下のようになります:

ルートからアドオンに至るデジタル署名のワークフロー。
ルートからアドオンに至るデジタル署名のワークフロー。

証明書には "subject" (被証明者, 署名を受ける主体) と "issuer" (発行者、署名を行う主体) が存在します。ルートにおいてはこの二つは同一ですが、他の証明書についてはその証明書に署名する証明書の subject が issuer となっています。

アドオンはそれぞれ別々のエンドエンティティ証明書によって署名されますが、ここで重要なのがほぼ全て1のアドオンが同じ中間証明書を共有している点です。この中間証明書こそが、今回問題となった証明書なのです。各証明書には有効期間があります。この期間にない証明書は受理されず、その証明書が署名したアドオンは Firefox に読み込めなくなります。不幸なことに私たちの使っている中間証明書の有効期限が 5 月 4 日午前 1 時 UTC で切れ、その瞬間を持ってその証明書に署名された全てのアドオンが検証不可能になり、Firefox に読み込むことができなくなりました。

アドオンの有効期限切れは全て深夜に起きましたが、アドオンがその瞬間に利用不可能になったというわけではありませんでした。Firefox がアドオンの有効性を常に確認しているわけではないためです。全てのアドオンはおよそ 24 時間ごとに確認されており、そのタイミングはユーザーごとに異なります。このため、すぐに問題と遭遇したユーザーもいればもっと後になってから遭遇したユーザーもいるという状況になりました。私たち Mozilla のスタッフが問題に気付いたのは 5 月 3 日金曜日太平洋標準時午後 6 時ごろであり、問題を解決するためのチームがすぐに集められました。

被害の防止

障害に直面した私たちは、事態がさらに悪くなるのを防ぐためいくつかの手立てを講じました。

まず、新しいアドオンの署名を停止しました。期限切れだと判明した証明書による署名が続いていたので、これは理にかなった判断でした。今考ればそのまま放っておいても大丈夫だったかもしれませんが、後で紹介する "日時を埋め込む" という緩和策と干渉するものであり、選択肢を残しておくのは良いことです (この緩和策は実行はされませんでしたが)。署名は停止されました。

次に、アドオンの署名の再確認を行わないようにするホットフィックスを迅速にプッシュしました。まだ証明書の再確認が行われていないユーザーに障害が及ぶのを防ぐためです。このフィックスは他のどんなフィックスよりも早く適用され、問題が解決された時点で取り除かれました。

並行して進める

理論上は、今回のような問題は簡単に解決できます。新しい有効な証明書を発行し、その証明書を使って全てのアドオンを署名し直せば良いのです。しかし残念ながら、私たちはこうできない理由をすぐに悟りました:

  1. アドオンが多すぎる (15,000 個以上) 。加えて署名サービスが大量の署名を行えるように最適化されていないので、全てのアドオンを再署名するのに時間がかかりすぎる。

  2. アドオンが署名された後、ユーザーはアドオンの新しいバージョンを入手しなければならない。Mozilla でホストされているアドオンについては Firefox が 24 時間以内にアップデートを自動的に行うが、他のソースからインストールされたアドオンは手動でアップデートする必要があり、とても不便である。

こうする代わりに、私たちはユーザーが操作することなく問題を解決する方法を見つけることに集中しました。

たくさんのアプローチを検討した結果、次の二つの戦略が良さそうだということになり、並行して進めていくことになりました:

  1. 証明書の検証に使う日時を変更するよう Firefox にパッチを当てる。こうすればアドオンは元通り動くようになるが、新しい Firefox をリリースしなればならない ("ドットリリース" である)。

  2. まだ有効期限が切れていない証明書で Firefox がどうにかして利用できるものを用意して、現在の有効期限が切れたものと入れ替える。

この時点の私たちにはどちらが上手く行くのかが分からなかったので、両方に同時に取り組み、最初に良さげになった方をデプロイすることに決まりました。その日の終わりには新しい証明書を用意する二つ目のフィックスをデプロイすることが決定します。これについてさらに見ていきましょう。

証明書の置き換え

このフィックスために踏むべきステップが二つあります:

  1. 新しい、有効な証明書を発行する。

  2. その証明書をリモートで Firefox にインストールする。

なぜこれが上手く行くのかを理解するには、Firefox がアドオンを検証する仕組みをもう少し知る必要があります。アドオンは様々なファイルのバンドルとして配布されますが、その中には署名に使われた証明書チェーンが含まれます。そのため Firefox のビルド時に組み込まれるルート証明書さえ分かれば、アドオンを独立して検証できるようになっています。ただし前述の通り今は中間証明書が機能していないので、アドオンの検証は不可能です。

しかしここで判明したのが、Firefox によるアドオンの検証に使われるのはアドオンに付属する証明書だけではないということです。Firefox はエンドエンティティ証明書から始まる信用できる証明書の鎖 (チェーン) を作ることで検証を行い、この鎖がルート証明書に到達するかどうかを判定していました。このアルゴリズムは入り組んでいますが、高いレベルで見たとき最初に起こるのは、エンドエンティティ証明書の issuer を subject に持つ証明書 (つまりは中間証明書) の検索です。通常はここで見つかるのはアドオンに付いてくる証明書チェーンに含まれる中間証明書ですが、ブラウザが認識できる証明書であれば他のものでも構いません。もし新しい有効な証明書をリモートで Firefox に追加できれば、その証明書も探索されます。次の図に新しい証明書を追加する前後の様子を示します。

新しい有効な証明書を発行する前後のワークフロー。
新しい有効な証明書を発行する前後のワークフロー。

新しい証明書がインストールされると、Firefox は証明書チェーンを検証する方法を二つ持つことになります。つまり有効期限の切れた古い証明書を使う (上手く行かない) 方法と、新しい有効な証明書を使う (上手く行く) 方法です。ここで重要なのが、エンドエンティティ証明書にあるシグネチャが有効になるためには、新しい証明書が古い証明書と同じ subject および同じパブリックキーを持たなければならない点です。幸運にも Firefox は上手く行くパスを見つけるまで両方のパスを試すので、証明書の追加さえできればアドオンを有効化できます。これは TLS 証明書の検証に使うのと同じ流れなのでコードは比較的良く理解されており、そのために私たちはコードを応用できたことを注記しておきます2

このフィックスが素晴らしいのは、既存のアドオンを変更しなくてよい点です。Firefox に新しい証明書を与えてやれば、古い証明書しか持たないアドオンも自動的に検証されます。Firefox に新しい証明書を追加するのが次の問題となりました。これをリモートで自動的に行うことができれば、後は無効化されているアドオンを再確認させればすみます。

Normandy と Studies

皮肉なことに、この問題を解決したのはシステムアドオン (SAO) と呼ばれる特殊なタイプのアドオンでした。研究調査のために私たちが開発した Normandy というシステムを使うと SAO を Firefox ユーザーに使わせることができます。SAO はユーザーのブラウザ上で自動的に実行され、通常は実験を行うために使われるのですが、SAO は Firefox の内部 API へのアクセスを大量に持っています。今の状況で重要なこととして、新しい証明書を Firefox の証明書データベースに追加することが可能です。このデータベースはアドオンの検証にも使われます3

よって次の二つのことを行う SAO を作れば良いということになります:

  1. 私たちが作成した新しい証明書をインストールする。

  2. ブラウザにアドオンの再検証を強制させて、無効化されたアドオンを有効にする。

ちょっと待った、とあなたが言うのが聞こえます。アドオンが使えないとしたら、どうやって SAO を実行させるんだ?...どうやって?新しい証明書で署名するのです!

全てをまとめる...どうしてこんなに時間がかかった?

OK. 計画は整いました: 古い証明書を置き換える新しいを証明書を作り、それをインストールするためのシステムアドオンを作り、Normandy を使ってそれをデプロイするのです。5 月 3 日の太平洋標準時午後 6 時から問題に取り組み始めて午前 2:44 に Normandy を使ったフィックスが完成し、9 時間もかかりませんでした。ただし多くのユーザーがそれを入手するのはさらに 6-12 時間が経過してからです。突然始まったプロジェクトにしてはこれはとても良い数字だと私は思っているのですが、Twitter では「もっと早くできなかったのか」という質問を多く受けました。実を言うと、時間がかかるステップがいくつもあったのです。

まず、新しい中間証明書の発行に時間がかかりました。前述した通りルート証明書はハードウェアセキュリティモジュールでオフラインに保存されています。ルートがごく稀にしか使用されず、これ以上なくセキュアである必要があることを考えればこれは素晴らしいセキュリティ対策なのですが、緊急時に新しい証明書を発行することになったときには明らかに不便です。いずれにせよエンジニアが一人 HSM が保存されているセキュアな場所まで車で行かなければなりませんでした。それから何度か間違った証明書を発行してしまったことが何度かあり、そのたびに次にすべきことを確かめたりテストを行うために一、二時間が消費されました。

次に、アドオンの開発に時間がかかりました。やっていることはとても単純なのですが、単純なプログラムであっても気を張って望まなくてはならず、事態を悪化させないことを何としても確信したかったのです。また SAO をユーザーに届ける前にはテストを行う必要があり、これも時間がかかりました。テストされる SAO が署名される必要があったのでなおさらです。署名システムが機能していないので、回避策を考えなくてはなりませんでした。

最後に、SAO をユーザーに届ける準備が整った後にもデプロイで時間を取られました。Firefox のクライアントは Normandy のアップデートを 6 時間ごとに確認しますが、当然多くのクライアントはオフラインなので、フィックスが Firefox のユーザー全体に伝わるまでには時間が必要でした。ただしこの時点で私たちは、ほとんどのユーザーは後で行うアップデートやドットリリースを受け取ることになるだろうと考えていました。

最後のステップ

Studies を使ってデプロイされた SAO はほとんどのユーザーの問題を解決するはずでしたが、全員の問題を解決したわけではありませんでした。具体的には、次のユーザーに別のアプローチが必要になりました:

最後のグループについては私たちにできることは何もありません ──古いバージョンの Firefox には深刻な脆弱性が修正されることなく残っているので、彼らはすぐに新しいバージョンに更新するべきです。古いスタイルのアドオンを使うユーザーが古いバージョンの Firefox に残りたがっているのは分かっていますが、そういったアドオンの多くは新しいバージョンの Firefox でも動作します。他のグループについては、新しい証明書をインストールするための Firefox のパッチを作成したので、ユーザーが更新を行えば問題が解決します。この更新は "ドットリリース" としてリリースされたので、ユーザーは通常の更新チャンネルを使って更新を入手できます ──おそらくは既に受け取っているでしょう。ただしダウンストリームのビルドを使っているなら、メンテナが更新を行うのを待つ必要があります。

以上の対応が完璧でないことは理解しています。特に、いくつかのケースではアドオンに関連するデータの消失が確認されました (例えば"マルチアカウントコンテナ"アドオン)。

この副作用を避ける修正は不可能でしたが、ほとんどのユーザーにとって短期的に一番のアプローチを取ることができたと私たちは信じています。長期的には、この種の問題を扱いやすくするためのアーキテクチャ上のアプローチを模索することになるでしょう。

教訓

私はまず、チームが素晴らしい仕事をしたと言いたいです。彼らは最初の報告から 12 時間以内にフィックスを作成し、ユーザーに届けました。この会議に参加した一人として、彼らはきつい状況でとても熱心に働き、無駄にした時間はほとんどなかったと言うことができます。

ただそうは言っても、この状況が理想的でなく、そもそもこのような問題が起こってはならないというのは明らかです。同様の事象が起こる可能性を下げ、起こったときの修正を容易にするという両面からプロセスを調整する必要があります。

私たちはきちんとした事後検討を来週に行い、これから行われる変更のリストを公開する予定です。なのでここでは、現時点で私が必要だと考えていることを挙げます。まず、Firefox が抱える全ての時限爆弾を追跡する手法を大幅に改善して、予期しない突然の爆発が無いようにしなければなりません。細かいことはまだ議論中ですが、少なくとも時限性を持つものの目録の作成は必須です。

それから、全てが故障してしまったときでも ──というより、そのようなときにこそ── ユーザーに素早く更新をプッシュできる仕組みが必要です。Studies システムが使えたのは良かったのですが、これはサービスとして公開できる完全なツールとは言えず、望ましくない副作用もありました。つまり、自動更新を有効にしていても Studies に参加していないユーザーは多くいたのです。これは全く問題の無いことです (告白: 私も無効化しています!) が、ユーザーに更新をプッシュするなんらかの手段が必要なのも確かです。内部の技術的メカニズムがどうであれ、ユーザーは (ホットフィックスを含む) アップデートにはオプトイン、それ以外の全てはオプトアウトできるべきです。また、更新チャンネルには現在よりも素早い反応が必要です。ホットフィックスとドットリリースをどちらも受け取っていないユーザーが月曜日になってもいくらかおり、完璧とは言い難い状況です。これについては作業が既に進んでいましたが、今回の事象がその重要性を示した形となりました。

最後に、アドオンのセキュリティアーキテクチャをより一般的な視点で考え直し、破損のリスクを最低限にする正しいセキュリティプロパティが実施されていることを確認する必要があります。

来週にはより詳細な事後検討の結果を追ってお伝えします。ですが今もし何か質問があれば、ekr-blog@mozilla.com にメールしていただければ喜んで回答します。

Eric Rescorla について

Eric は Mozilla の Firefox チームの CTO です。

編集履歴

5 月 9 日 8:22 pt - (1) 動詞の時制を修正 (2) ダウンストリームのディストリビューションに関する状況を明確化 (詳細はBug 1549886を参照)


  1. いくつかのとても古いアドオンは異なる中間証明書を使います。 ↩︎

  2. WebPKI に慣れ親しんでいる読者には、この流れが相互認証と似て見えるはずです。 ↩︎

  3. 技術メモ: 特殊な権限を持つ証明書を追加しているのではありません。Firefox が利用可能な証明書プールに追加しているだけであり、特権を持つ証明書を新たに追加するのとは異なります。 ↩︎

広告