「Firefox」でインストール済みアドオンが利用不能になる問題が発生中 - 窓の杜やFirefox 66.0.4 リリース、拡張機能全滅問題を修正 | スラド ITなどで既報の通り、2019年5月4日前後から一般向けのリリース版Firefoxでアドオンを一切使用できない状態が発生していました。緊急の修正版としてリリースされたFirefox 66.0.4およびFirefox ESR60.6.2で現象は既に解消されており、また、既にインストール済みのFirefoxに対してもホットフィックスが順次展開されていますが、本記事では今回の問題の技術的な詳細を解説します。
まず最初に、ユーザー側で取れる対応をまとめておきます。
hotfix-update-xpi-signing-intermediate-bug-1548973
が自動的に反映されるのを待つ。about:config
で隠し設定の xpinstall.signatures.required
を false
に設定する。(Firefox ESR、Developer Edition、Nightlyのみ有効で、Firefox 48以降の通常のリリース版Firefoxではこの方法は無効です)Mozillaによるサポートが終了したバージョンのFirefoxでは「Firefox調査」を通じての修正の反映が行われないため、Firefoxの更新、証明書の手動でのインポート操作、または about:config
での隠し設定の変更が必要になります。
修正が反映される前のバージョンのFirefoxを起動してアドオンが無効化されてしまった場合、証明書のインポートや about:config
での暫定的対応を行った後でも「アドオンが依然として無効で、且つ、ユーザー操作で再度有効化できないまま」となる場合があります。これは、アドオンが署名の期限切れで強制的に無効化されたという情報がユーザープロファイル内に残留しているために発生する現象です。この状況に陥った場合、Firefoxを終了した状態でユーザープロファイル内の extensions.json
を削除して状態をリセットすると、アドオンが自動的に有効化されます(あるいは、手動操作で有効化できる状態でアドオンマネージャに表示されるようになります)。
Firefoxでは、Firefox 43以降のバージョンからアドオンのインストールパッケージの電子署名が必須となっています。
Firefoxのアドオンはその特性上、認証局証明書の差し替えや通信の読み取りなど、Firefox上でありとあらゆる事ができる物でした*1。これではユーザーを困らせたりユーザーの機密情報を狙ったりする事を企む攻撃者にとって格好の標的となり得るため、その種の攻撃を防ぐために、Firefox 43で以下のような仕組みが導入されました。
ただ、これではアドオンの開発中や、社内用として機密情報を含めたアドオンを使いたい場合など、審査用にファイルを供出できないケースで支障があります。そのため、以下のような救済措置が存在しています。
xpinstall.signatures.required
という隠し設定を false
にして無効化できる。
about:debugging
から一時的に読み込まれたアドオンに対しては、電子署名の検証は行われない。ここでさらっと「電子署名が施される」と述べましたが、具体的には何が行われているのでしょうか。IE View WEの場合を例に取って解説します。
「インストール」ボタンからダウンロードできるインストールパッケージ ie_view_we-1.3.2-fx.xpi
の実体はZIP形式のアーカイブですが、これを展開して取り出された中にある META-INF
というフォルダとその内容がまさに電子署名の実体です。それぞれの内容は以下の通りです。
META-INF/manifest.mf
: パッケージ内に含まれる各ファイルのハッシュ値の一覧META-INF/mozilla.mf
: パッケージ全体のハッシュ値META-INF/mozilla.rsa
: DER形式の電子署名データこのうちの mozilla.rsa
には、以下の情報が含まれています。
ユーザー環境のFirefoxは、実際のアドオンのパッケージに含まれるファイルの内容と公開鍵を使って署名データを導出し、それが mozilla.rsa
に含まれる物(秘密鍵を使って導出された物)と一致するかどうかを以て、パッケージの内容が改竄されていないことを確認することができます。
この mozilla.rsa
に含まれる証明書は、openssl
コマンドを使うと以下のようにして内容を確認できます。
$ openssl pkcs7 -inform DER -text -print_certs < ie_view_we-1.3.2-fx/META-INF/mozilla.rsa
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1551780906376321839 (0x158908833dcb5b2f)
Signature Algorithm: sha384WithRSAEncryption
Issuer: C=US, O=Mozilla Corporation, OU=Mozilla AMO Production Signing Service, CN=signingca1.addons.mozilla.org/emailAddress=foxsec@mozilla.com
Validity
Not Before: Mar 5 10:15:06 2019 GMT
Not After : Mar 4 10:15:06 2020 GMT
Subject: C=US, ST=CA, L=Mountain View, O=Addons, OU=Production, CN=ieview-we@clear-code.com
Subject Public Key Info:
(省略)
X509v3 extensions:
(省略)
Signature Algorithm: sha384WithRSAEncryption
(省略)
-----BEGIN CERTIFICATE-----
(省略)
-----END CERTIFICATE-----
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1048580 (0x100004)
Signature Algorithm: sha384WithRSAEncryption
Issuer: C=US, O=Mozilla Corporation, OU=Mozilla AMO Production Signing Service, CN=root-ca-production-amo
Validity
Not Before: May 4 00:09:46 2017 GMT
Not After : May 4 00:09:46 2019 GMT
Subject: C=US, O=Mozilla Corporation, OU=Mozilla AMO Production Signing Service, CN=signingca1.addons.mozilla.org/emailAddress=foxsec@mozilla.com
Subject Public Key Info:
(省略)
X509v3 extensions:
(省略)
Signature Algorithm: sha384WithRSAEncryption
(省略)
-----BEGIN CERTIFICATE-----
(省略)
-----END CERTIFICATE-----
ここでは2つの証明書が出力されています。
CN=ieview-we@clear-code.com
である証明書(CN=signingca1.addons.mozilla.org
によって発行された):そのアドオン固有の物として発行されたコード署名証明書で、パッケージの電子署名に使われた物CN=signingca1.addons.mozilla.org
である証明書(CN=root-ca-production-amo
によって発行された):中間証明書CN=root-ca-production-amo
の証明書は addons-public.crt
というファイル名でFirefoxのリポジトリに含まれている自己署名証明書で、ビルド時にソースコード内に組み込まれ、組み込みの証明書として認識される、ビルトインのルート証明書の1つです*2。
ここで注目するべきなのは、mozilla.rsa
に含まれる証明書のうち2番目、中間証明書の方の有効期限です。当該箇所を以下に抜粋します。
Not Before: May 4 00:09:46 2017 GMT
Not After : May 4 00:09:46 2019 GMT
これを見ると、有効期限が2017年5月4日0時9分46秒(UTC)から2019年5月4日0時9分46秒(UTC)となっています。日本標準時はUTCより9時間進んでいるので、日本では2019年5月4日の9時過ぎに有効期限を迎えた事になります。この瞬間以降、この中間証明書を含むアドオンのパッケージはすべて「期限切れの中間証明書に基づく証明書で署名されたファイル」と見なされ、「署名を検証できない」=「エディターが審査した通りの内容のファイルであると確認できない」=「安全が確認されていない」と判断されるため、新規インストールは拒絶され、インストール済みの物は無効化されるという結果になっていたわけです。
上記の事を踏まえ、Mozillaではいくつかの対応が検討されたようです。FirefoxのリリースブランチとESR60用ブランチに実際に投入された変更を追うと、以下の3通りの方法が試された模様です。
CN=root-ca-production-amo
の証明書に基づく証明書ツリーを検証する時に新しい中間証明書を代わりに使う緊急のリリースとなったFirefox 66.0.4とFirefox ESR60.6.2には、この2番目の対応が反映されています。また、動的な反映が可能である事から、統計情報の収集・ユーザーのFirefox使用状況の収集を行うための仕組みを使って、この2番目の方法と同じ手順でインストール済みのFirefoxに暫定的対策を行う(有効な中間証明書を自動インポートする)という対応が展開されています。Firefox 66.0.3以前やFirefox ESR60.6.1以前を使用中の環境では、この対応が反映された事によってアドオンが再び使用可能になっているはずです。「オプション」→「プライバシーとセキュリティ」→「証明書」→「証明書を表示」で認証局証明書の一覧を表示し、Mozilla Corporation
配下に signingca1.addons.mozilla.org
が表示されていれば、この対応が既に反映された状態であるという事を確認できます。
一方、Firefox 66.0.5とFirefox ESR60.6.3には3番目の対応が採用されており、2番目の対応は不要になったとして取り除かれています*4。
現時点では以上の通りの暫定的な対応がなされていますが、今回発生したすべての問題が解決されたわけではなく、一部のアドオンではさらに手動での対応が必要です。その中でも、アナウンスされている未解決の問題の1つである、一部のアドオンが中間証明書の更新後も有効にならない件は、ここまでで解説した電子署名の仕組みが大きく関係しています。
前述の説明の通り、アドオンは中間証明書によって発行されたそのアドオン固有の証明書によって電子署名を施された状態になっています。この証明書はアドオンごとの固有の識別子(ID)に基づいて発行されていますが、EPUBReaderなどの一部のアドオンでは、そもそもこの固有の識別子が開発元によって指定されていません。そのため、Mozilla Add-onsのシステムが自動的に一意なIDを割り当てているのですが、アドオンのファイルの最終更新日が変わったなどの契機で自動割り当てされたIDが代わってしまうと、中間証明書の更新後であっても、そもそも電子署名が不正と見なされてしまうという状況が発生します。そのため、アドオンを手動で再インストールして、電子署名に記録されたIDとアドオンの一時的なIDが一致する状態を作り直す必要があるという訳です。
他にも、コンテナータブに固有の情報が失われる、検索エンジンや起動時のホームページなどアドオンによって変更されたはずのFirefox側の設定が初期状態に戻ったままになる、といった問題が残されているようです。これらの問題が解決された場合、それらもまたホットフィックスとして展開されたり、もしくは緊急の修正版としてリリースされたりする可能性がありますので、引き続き注視が必要です。
冒頭に記載したとおり、Firefox ESR、Developer EditionおよびNightlyにおいては、 xpinstall.signatures.required
による電子署名の検証の無効化によって今回の問題を回避できます。しかしながら、それは同時に、この設定と同時に導入された安全確認の仕組みを無効化する事をも意味します。企業での運用でユーザーによるアドオンのインストールを禁止していて、導入されるアプリケーションの影響範囲が厳密に制御されている状況であればよいのですが、個人でESR版を使用している場合、悪意の攻撃者によるアドオンが知らず知らずのうちに読み込まれるというリスクがあります。
この方法を取った場合は、Firefoxが更新されたり、ホットフィックスが反映されたりした事を確認できたら、早急に証明書の検証を有効化する事を強くお勧めします。
以上、2019年5月4日以降で発生しているFirefoxのアドオン無効化問題の技術的な詳細を解説しました。
当社ではFirefoxの法人向けテクニカルサポートを有償で提供していますが、実際にお客様の環境でも影響が出ている例があります。
今回の問題に見られるような証明書の期限切れに起因する問題は、度々世間を騒がせています。記憶に新しい所では、2018年から2019年にかけても以下のニュースがありました。
証明書のように有効期限が存在するデータに依存するシステムを運用する場合、有効期限の到来には充分な余裕を持って対応できるような仕組みを作っておく必要がある、という事を思い知らされますね。
*1 これは従来型のアドオン(いわゆるXULアドオン)での話です。Firefox 57以降のバージョンで使用できるアドオン(WebExtensionsベースのアドオン)では、できる事が技術的に制限されているため、危険性は大幅に減じています。
*2 編集履歴によると、このルート証明書は2015年に電子署名の仕組みが導入された時に追加されて以降特に変更された様子はありません。ルート証明書自体は2025年3月15日まで有効なので当面は問題無く利用できますが、このファイルがもし今後更新されないままであれば、2025年に再び同様のトラブルが起こる事になるでしょう。その場合、署名の検証を無効化できなくなっている現在のリリース版Firefoxをサポート切れのまま使い続けていると、アドオンを再度有効化する手立てが全く無くなるという事にもなりかねません。これらの観点からも、サポート切れのバージョンのFirefoxを使い続ける事にはリスクがあり、原則としてサポートが継続しているバージョンを使い続ける事が望ましいという事が言えます。
*3 各アドオンの署名データに含まれる中間証明書と同じSubjectの中間証明書がFirefoxの証明書データベースにも含まれるようになり、以後はそちらが優先的に認識されるために中間証明書の期限切れ問題が解消する、という理屈です。
*4 一度証明書データベース登録された中間証明書は、明示的に削除しない限りはFirefox 66.0.5への更新後もそのまま残ります。
ゴールデンウィークの前のことになりますが、SciPy Japan Conference 2019でApache Arrowの紹介をした須藤です。
関連リンク:
まず、ふだんほとんどPythonを書いていない私がどうしてSciPy Japan Conference 2019で話すことになったのか、その経緯を説明します。
もともと、Wes McKinneyさんにオファーがありました。彼はPythonでよく使われているpandasの作者でもあり、Apache Arrowの主要開発者でもあります。SciPy Japan Conference 2019でApache Arrowの紹介をする人としてはうってつけです。しかし、残念ながらSciPy Japan Conference 2019の開催日に日本に来ることができませんでした。そこで、彼が私を紹介しました。私もApache Arrowの主要開発者の1人(2019-04-23時点ではApache Arrowへのコミット数は2位)で、私は東京に住んでいるからです。
ということで、Wesさんからの紹介で私がApache Arrowの紹介をすることになりました。
Pythonのカンファレンスなので、Apache Arrowの一般的な話というより、Pythonユーザーにはどううれしくなりそうかという観点で紹介したつもりです。具体的には近い将来うれしくなりそうな点として次の2点を紹介しました。
すでにこれらの点を実現しているプロダクトがあります。
たとえば、Sparkはpickleの代わりにApache Arrowを使うことで100倍以上高速化しています。
参考:Speeding up PySpark with Apache Arrow
たとえば、VaexというデータフレームライブラリーはApache Arrowを(も)使うことでpandasよりも高速に文字列を処理できるようになっています。
参考:Vaex: A DataFrame with super strings
また、どうして高速になるか、大量のデータを扱えるようになるかの理由も説明しました。詳細はスライドやApache Arrowの最新情報(2018年9月版)やApache Arrow東京ミートアップ2018 - Apache Arrowを参照してください。
SciPy Japan Conferenceは今回が初めての開催ということで参加者は100人いかないくらいでした。PyCon JPと比べると小さな規模です。(PyCon JP 2018は1000人以上の参加者です。)初めての開催ということであまり知られていなかったこととサイエンスに特化した内容ということが影響しているのかと思います。
私は初めてPythonのイベントに参加しましたが、とても国際カンファレンスですごいなぁと思いました。私は海外で開催されている国際カンファレンスには参加したことがなく、参加したことがある国際カンファレンスはRubyKaigiだけなのですが、海外で開催されている国際カンファレンスはこんな感じなのかなぁと思いました。
SciPy Japan Conference 2019の運営をしていた方々から話を聞いたところ、SciPy Japan Conference 2019は本家のSciPy Conferenceを運営している人たちが運営しているということでした。本家のSciPy Conferenceについても教えてもらえました。「今回は機械学習の話題が多かったけど、本家のSciPy Conferenceは機械学習だけでなくサイエンス全般の話題を扱っているんだよ。地球のこととか。SciPy Japan Conferenceもサイエンス全般の話題を扱えるようになるといいな。」みたいな話を聞いてすごくおもしろそうだなぁと思いました。地球のこととかおもしろそう!Rubyでもそんなカンファレンスがあるとおもしろそう!
午前中に3時間のチュートリアルがあるのもおもしろかったです。Rubyのカンファレンスでもやりたいな。(すぐRubyのことを考えてしまう。)
午後はトークでした。知らないことばかりだったので非常に興味深かったです。Pythonではいろんなことが簡単にできるようになっていてワクワクしますね!Rubyでもそうなるといいな。
せっかくいろいろ知れたのでSciPy Japan Conference 2019の最中にこっそりOptunaのRubyバインディングであるRed Optunaを作りました。SciPy Japan Conference 2019に参加してよかったです。Red Optunaのアイディアは初日のレセプションで佐野さんと話しているときにでてきたアイディアです。話せてよかったです。佐野さんは2日目にOptunaのトークをしました。
また参加したいと思ったカンファレンスでした。
SciPy Japan Conference 2019で日本のPythonユーザーのみなさんにApache Arrowを紹介しました。Rubyコミュニティー以外にもApache Arrowのことを紹介したいのでApache Arrowのことを知りたくなったら私に声をかけてください!
私はデータ処理ツールの開発という仕事をしたいと思っています。その中にはもちろんApache Arrowの開発も含まれています。一緒に仕事をしたい!(自社サービスをApache Arrow対応したいとか)という方はお問い合わせフォームからご連絡ください。
恐らくあまり知られていませんが、Firefoxはインストール先の browser/chrome/icons/default/main-window.ico
の位置(Firefoxのインストール先が C:\Program Files\Mozilla Firefox\
であれば、 C:\Program Files\Mozilla Firefox\browser\chrome\icons\default\main-window.ico
)にICO形式のアイコン画像を置くと、それがメインウィンドウのアイコンになるという機能がありました*1。
この機能について、次のESR版であるESR68でも有効なのかどうかを確かめようとNightly 68で試してみたところ、アイコンが変化しないという結果を得られました。
ということは、Firefox ESR68では機能が削除されたのだな……と決めつけるのは早計です。開発版のNightlyで駄目だったからといって、そのバージョンのFirefoxが正式リリースされた後も駄目だとは限りません*2。たまたまNightlyでは機能していないだけなのか、それともこの方法が使えなくなってしまったのか、きちんと裏付けを取らないとはっきりした事は言えません。
この記事では、フリーソフトウェア・OSSで遭遇したこのような挙動の変化の経緯を実装を辿って調べる方法について、Firefoxでの事例を示します。
この事例では「ウィンドウのアイコン」がポイントになります。Windows上で動くFirefoxはWindowsのアプリケーションなので、ウィンドウのタイトルバーのアイコンを変えるには当然Windowsのアプリケーションの流儀に則った方法を使っているはずです。
Windows window icon win32
といったキーワードで検索してみたところ、Win32アプリケーションでウィンドウのアイコンを変える伝統的なやり方に WM_SETICON
という定数で定められたメッセージを使う方法 があるという事が分かりました。
確認してみたところ、前述の方法でのアイコン変更はFirefox ESR60やFirefox 66では機能していました。そこで、Firefox ESR60のソースコード中で SM_SETICON
という定数が使われている箇所を検索してみたところ、クロスプラットフォームな実装ではなくWindows固有の実装の部分で、この定数が使われている箇所が見つかりました。
このメソッドの冒頭で呼ばれているResolveIconName()
というメソッドの定義を辿り、さらにその中で呼ばれている ResolveIconNameHelper()
というメソッドの定義まで辿ってみたところ、「与えられたファイルハンドラからicons
、default
、とディレクトリを辿り、与えられたファイル名のアイコンファイルに対応するアファイルハンドラを得る」という処理が行われている様子がうかがえました。これはまさしく、前述の「特定の位置に置かれたアイコンファイルを参照する」という処理そのものに見えます。
この箇所は nsWindow
クラスの SetIcon()
メソッドに含まれていますので、今度はこのメソッドを呼んでいる箇所を探しました。すると、XULのwindow
要素のid
属性の値を参照してアイコンを設定しているらしい箇所が見つかりました。
// "id" attribute for icon
windowElement->GetAttribute(NS_LITERAL_STRING("id"), attr);
if (attr.IsEmpty()) {
attr.AssignLiteral("default");
}
mWindow->SetIcon(attr);
ということで、先の方法でウィンドウのアイコンを変更するという機能は、この一連の箇所によって実現されている事が分かりました。
機能が動いているバージョンでの実装が分かったので、今度は対応する部分のNightly 68での実装を見てみました。
先ほど見ていった実装のそれぞれについてNightly 68での状況を見ていくと、ほとんどの箇所には変更が見られませんでしたが、nsWindow
クラスの SetIcon()
メソッドを呼んでいる箇所に違いがありました。こちらでは、XULのwindow
要素のicon
属性の値を参照して、値があった時にだけアイコンを設定するというコードになっています。
// "icon" attribute
windowElement->GetAttribute(NS_LITERAL_STRING("icon"), attr);
if (!attr.IsEmpty()) {
mWindow->SetIcon(attr);
NS_ENSURE_TRUE_VOID(mWindow);
}
Firefoxのソースコード検索システムでは、そのまま続けてMercurialのblameの結果を見る事ができます。そこから当該箇所の変更が行われたコミットの情報を見ると、バグトラッカー上での対応するbugの番号が記載されており、Bug 1531836 - Each new xul window does a stat call to look for non-existant window specific iconsに辿り着きました。
bugの内容を読むと、どうやら起動処理の高速化の一環として、「まず存在しないファイルを探しに行く」のではなく「明示的にアイコンが指定されている時だけファイルを探しに行く」という方針に改める変更が行われ、その一環として前述の機能が削除されたという事のようです。
以上、フリーソフトウェア・OSSで「以前のバージョンでは使えていた機能が新バージョンでは使えなくなった」という事態に遭遇した時の原因の調べ方の、Firefoxでの事例をご紹介しました。
ユーザーの目に触れやすい機能の変更はリリースノートに記載されることが多いですし、また、開発者向けに影響の大きな変更は技術情報が別途まとめられている事もあります。しかし、この例のように影響度の小さい変更は、そのような変更があったという事自体は特に告知されないままになっている事があります。そういった場合でも、フリーソフトウェア・OSSではソースコードやバージョン管理システムの変更履歴を辿って、原因を特定したり回避方法を見つけたりする事ができます。
クリアコードではフリーソフトウェア・OSSに対する技術サポートを有償にて提供しており、お客様からの「今まで使えていた機能が使えなくなった事の理由を調べて欲しい」「回避策があれば、それを教えて欲しい」といったご依頼を受けてこのような調査も行っています。
業務上でのフリーソフトウェア・OSSの利用でお困りの方は、是非メールフォームからご相談下さい。
*1 正確には、`(id文字列).ico` という名前でファイルを置いておくと、ウィンドウの内部的なID(XULの`window`要素の`id`属性の値)が一致するファイルが自動的に使われるという事になっています。
*2 その逆に、Nightlyでは使えていた機能が同じバージョンのリリース版Firefoxで使えなくなる場合もあります。例えば `xpinstall.signatures.required` という設定はビルド時のオプションによって機能するかどうかが切り替わるようになっており、NightlyとDeveloper Editionでは使えますが、ベータ版および正式リリース版のFirefoxでは機能しないようになっています。
皆さんはCardDAVという仕様をご存じでしょうか? CardDAVはWebDAVのプロトコルを使ってLDIF形式のアドレス帳をやり取りするという物で、これを用いると「読み書き両方を行えて、内容が複数PC間で同期される」という種類のリモートアドレス帳を汎用の物として実現することができます。CardDAVサーバーとして振る舞える製品にはownCloudやDAViCalなどがあり、読み取り専用に設定したリモートアドレス帳を複数人で共有するという事もできますので、企業利用では重宝する場面がありそうです。
このように便利なCardDAVですが、残念ながらThunderbirdは本体の機能としては対応していません。CardDAVベースのリモートアドレス帳を使うにはアドオンをインストールする必要があります。CardBookは、そのようなCardDAV対応のためのアドオンの一例です。
ところで、企業によっては個人情報の取り扱い方について、「顧客や取引先の個人情報をローカルに保存する際は必ず暗号化する」といったプライバシーポリシーを定めている場合があります。前出のCardBookはリモートアドレス帳のデータをIndexedDBを使用してローカルにキャッシュする設計で、この時のデータは暗号化されないため、そのままでは前述のポリシーに抵触するので採用できないという事になります。
そのような背景から、「CardBookでローカルに保存されるデータを暗号化したい」というご相談を頂き、成果を開発元に還元する前提で先行して作業を進めていたのですが、残念ながら受注には至らず、手元には実現可能性の調査のために行った試験的な実装が残るという結果になりました。しかしせっかく実装した物をそのまま放置しておくのも勿体なかったので、CardBookプロジェクトに還元したところ、標準機能の1つとして取り込まれるに至りました。現在リリース済みのCardBook 33.9以降のバージョンでは、設定画面でチェックボックスをONにすればローカルデータの暗号化が有効になるようになっています。
以下、CardBookのローカルデータベースの暗号化を実現するにあたって行った具体的な内容をご紹介します。
幸い、Thunderbirdの基盤であるGeckoには、暗号化のための汎用APIであるWeb Crypto APIが実装されています。あるのなら使わない理由はありませんので、CardBookでもデータの暗号化はWeb Crypto APIによる共通鍵暗号で行う事にしました。何故公開鍵暗号ではなく共通鍵暗号なのかについては別項で詳しく述べていますので、そちらも併せてご覧下さい。
実装は、まず暗号化・復号を行う専用のモジュールを追加した上で、IndexedDBの読み書きを行うモジュールの書き込み用のデータを用意する箇所に暗号化処理を、読み込んだデータを検証する箇所に復号処理を仕掛けることで、他のモジュールに影響を与えず透過的に動作するような組み込み方としました。
この時気をつけなくてはならないポイントとして、暗号化をどのタイミングで行うかという点が挙げられます。以下、暗号化を行っている実際の箇所を抜粋しながら説明します。
元々の設計では、IndexedDBへのデータ書き込みは以下の要領で行われていました。
addCard: unction (aDirPrefName, aCard, aMode) {
var db = cardbookRepository.cardbookDatabase.db;
// トランザクション開始
var transaction = db.transaction(["cards"], "readwrite");
var store = transaction.objectStore("cards");
var storedCard = aCard;
// データの書き込み
var cursorRequest = store.put(storedCard);
// 以下、成功時・エラー時の処理
}
ここに暗号化処理を組み込むのですが、Web Crypto APIは暗号化したデータがPromiseで返されるため、値を使うには.then()
のコールバック関数で受け取るか、await
で値の解決を待つ必要があります。コールバック関数を使うスタイルで実装するにはこのメソッドの書き方を大幅に変えなくてはなりませんが、async
キーワードとawait
を使うと、この同期処理の関数を容易に非同期処理に対応させることができます。
addCard: async function (aDirPrefName, aCard, aMode) { // asyncキーワードを追加
var db = cardbookRepository.cardbookDatabase.db;
// トランザクション開始
var transaction = db.transaction(["cards"], "readwrite");
var store = transaction.objectStore("cards");
// 暗号化処理を追加
var storedCard = cardbookIndexedDB.encryptionEnabled ? (await cardbookEncryptor.encryptCard(aCard)) : aCard;
// データの書き込み
var cursorRequest = store.put(storedCard);
// 以下、成功時・エラー時の処理
}
当初はこの例のように、書き込みを行う直前で暗号化を行うようにしていました。しかし実際に動作させてみると、これではIndexedDBでのデータ書き込みに失敗するという結果になりました。何故でしょうか?
実は、IndexedDBでのデータ書き込みはトランザクション開始から書き込みまでを同期的に(同じイベントループ内で)行う必要があります。この例ではトランザクション開始後にawait
を使ってしまっているせいで、store.put(storedCard)
が次のイベントループでの実行となってしまい、そのせいで書き込みに失敗してしまうという訳です。
そのため最終的な実装では、以下の例のようにトランザクション開始前に暗号化を終えておくようにしました。
addCard: async function (aDirPrefName, aCard, aMode) {
// 暗号化
var storedCard = cardbookIndexedDB.encryptionEnabled ? (await cardbookEncryptor.encryptCard(aCard)) : aCard;
var db = cardbookRepository.cardbookDatabase.db;
// トランザクション開始
var transaction = db.transaction(["cards"], "readwrite");
var store = transaction.objectStore("cards");
// データの書き込み
var cursorRequest = store.put(storedCard);
// 以下、成功時・エラー時の処理
}
これなら、トランザクション開始から書き込みまでが同期的に行われるため問題ありません。
復号時には、特にこのような注意は必要ありません。また、元々IndexedDBからのデータ読み取りは結果が非同期で返されるので、CardBookのデータ読み込み処理もその前提で設計されていました。そのため、IndexedDBから返ってきたデータを非同期で復号した上で返却するという処理を挟み込んでも、CardBookのデータ読み込み処理全体としてはインターフェースを変えずに済んだのでした。
CardBookのローカルデータ暗号化では、暗号化・復号に使う共通鍵は、バックグラウンドで自動生成した物を暗黙的に使い、鍵自体をユーザープロファイル内に保存する形としました。
「暗号化されたデータと鍵を同じ場所に置いておくのでは、暗号化の意味が無いじゃないか」と思うでしょうか? 実際、変更をフィードバックした際にもCardBookプロジェクトの開発者の方からも「パスワード入力を求める方式にした方がいいのではないか?」という質問がありました。Web Crypto APIの機能を使うとユーザーが入力したパスワードから秘密鍵を作る事もできる(Web Crypto APIの解説記事の「パスワードを鍵に変換する」の項をご参照下さい)のに、そうしなかったのは何故でしょうか。
ここで一旦、パスワードの安全な運用という事を考えてみましょう。パスワードを自分で記憶しておきその都度入力するという方式は、一見すると安全なように思えます。しかしながら、運用の仕方によっては却って危険になる場合があります。
これらの理由から、パスワードの入力は「複雑で憶えにくいパスワードを1つだけ覚える」「それをマスターパスワードとして使い、それ以外はパスワードマネージャに憶えさせる」という運用にするのが比較的安全だというのが現在の定説となっています*1。
「企業でThunderbirdを使う」というシチュエーションでは、「PCのログオン」「受信メールサーバーの認証」「送信メールサーバーの認証」などでそれぞれパスワードの入力が発生する可能性があります。という事は、ここにさらに「ローカルデータの復号」のためのパスワードが加わるというのは、さすがに実運用を妨げるレベルの煩わしさでしょう。かといって、他の部分ではパスワードを使用していないのにここでだけパスワードの入力を求める、というアンバランスな運用も考えにくいです。そういった事を考慮した結果として、CardBookのローカルデータ暗号化は現在比較的安全とされている運用を想定し、
というポリシーを採用する事にしたのでした。
Thunderbird用アドオンのCardBookに対して行った、ローカルデータの暗号化対応の概要をご紹介しました。
当社では、一般に公開されているFirefox用アドオン・Thunderbird用アドオンをはじめとした様々なフリーソフトウェア・OSSについて機能追加・改造のご依頼を承っております。また、成果をアップストリームに還元しても差し支えがないケースでは、積極的に還元を行うようにしています。自社でフリーソフトウェア・OSSを採用したいが少しだけ要件に合わない、という事でお悩みの場合には、メールフォームからお問い合わせ下さい。
*1 ただし、これはあくまで現時点での話です。技術の進歩や、この分野での研究が進む事などによって、「最もマシ」なやり方は変わっていく可能性があります。
結城です。
当社のOSS開発支援サービスではこれまで、SpeeeさんでのOSS開発支援やご相談窓口の設置、Supershipさまでの研修の実施などに携わって参りました。この記事ではその新たな事例として、株式会社アカツキさまで昨年および本年の新人研修の一環として開催した OSS Gate のワークショップを社内にて複数回開催させていただいた件をご紹介します。
株式会社アカツキさまの事業の柱の1つはゲームの開発・運営です。お話を伺ったところ、ゲーム業界では(少なくとも、ファミコンに端を発するコンソールゲームの業界では)伝統的に、会社ごとに自社開発のライブラリやノウハウを抱え込む事が多かった事から、開発の成果を公開したり社外に開発の中心があるOSSの開発に参加したりという事はそれほど積極的には行われない傾向があるとのことでした。
しかし、現代のゲーム開発では技術が1社だけに閉じないケースが多くなっています。オンラインゲームではサーバー側の技術基盤は一般的なWebサービスと変わりませんし、クライアント側においてもUnityのスクリプトエンジンが.NET FrameworkのOSS実装のMonoであったりします。また、ゲーム本体から周囲に目を向ければ、そもそも開発に使用するGitやVisualStudio CodeなどのツールもOSSです。このように、サービス提供も開発も今やOSS無しではままならないというのが実情です。こういった背景があり、アカツキさまでは「会社としてOSS活動を推進していきたい」という意気が高まっていたそうです。
これを踏まえて、アカツキさまの技術アドバイザーとSpeeeさまの技術顧問を兼任されている井原さんから当社をご紹介頂き、アカツキさまのOSS活動推進の取り組みをお手伝いさせて頂くこととなりました。その際、具体的な取り組み方について相談した結果、まだOSS活動に関わったことがない人が多い・(今後の進め方を考えるために)当社が現状を把握したいという理由から、まずは当社メンバーが案内役を務める形で OSS Gate ワークショップをアカツキさま社内で開催することとしました。
OSS Gate ワークショップは、参加者を「まだOSS開発に関わった事が無い人=ビギナー」と「OSS開発に関わった経験がある人=サポーター」に分けて、ビギナーが主体的に作業を進めるのをサポーターが手助けする*1という形で、バグ報告やパッチの提供などの「フィードバック」をする体験をして頂くイベントです。今回は、ビギナーとしては主に新入社員の方や中途採用されて日が浅い方に多くご参加頂く結果となりました。また、通常の公開で行う OSS Gate ワークショップではサポーターは広くボランティアを募っていますが、今回はサポーターも皆アカツキさま社内の方に務めて頂きたいという事で、こちらも社内で希望者を募ったり、アカツキ内でこの取り組みの主担当をされているシニアエンジニアの島村さんから指名して頂いたりしました。これは、サポートする側を経験して頂く事により、普段から業務の中で他の方のOSS開発への参加を手助けして頂きやすくなればという目論見があったためです。
開催形態は東京で開催している OSS Gate ワークショップの形態を踏襲し、1人のサポーターが1~2人のビギナーと組む形としました*2。参加希望者のスケジュールが合う日がバラバラだったため複数の日に分けて開催することとし、2018年には4回、2019年には3回に分けて実施しました。
ワークショップは通常は1日がかりで行いますが、今回はアカツキさまの業務の都合もあり、内容を一部省略して所要時間を2/3~半分程度に短縮しての実施としました*3。
各回の参加者アンケートは OSS Gate ワークショップのリポジトリで公開されており、以下から閲覧可能です。
OSS Gate ワークショップはフィードバックの過程を体験して頂くという趣旨のイベントなので、バグ報告やパッチ提出などの形に残る結果を出すことを必須には設定していません。そのため、通常の OSS Gate ワークショップはフィードバックに至る手前で時間切れになる方も多いです*4。ただ、アカツキさまでの開催分については研修で前提知識が共有されている部分が多いためか躓きはあまり発生せず、ほとんどの方が時間内に実際にフィードバックを行う段階まで到達されていました。
筆者が印象的だった出来事としては、ビギナーの方が選択を検討されていたソフトウェアが実はOSSではなかった、という事例がありました。ゲーム業界ということでUnityを使われている方が多く、その方は「ユニティちゃんライセンス」が設定されたプロジェクト(unity3d-jp/unitychan-crsなど)へのフィードバックを検討されていたのですが、調べてみると、当該ライセンスはOSIが認定済みのOSSライセンス一覧には掲載されていませんでした*5。この事例のように、GitHubにリポジトリがある・ソースコードが閲覧可能な状態になっているからといってOSSであるとは限らない、という点は意外と見落としてしまいがちなので、皆さんもくれぐれもご注意下さい*6。
なお、この一連のワークショップが契機となってか、後に参加者の方が新たにOSSを公開されたという事を後からお知らせ頂きました*7。OSS Gate ワークショップではフィードバックをする際の注意点として「作者(プロジェクト運営者)にとって分かりやすくなるように報告内容を整理する」という事を度々伝えていますが、自分自身が作者としてOSSを公開する側になると、実際にフィードバックを受ける際にどのようなフィードバックであれば助かるかという事を、より実感を持って考えられるようになるはずです。得るものは多いと思われますので、開発したソフトウェアをOSSとして公開する事にも、皆さんには是非チャレンジして頂きたいです。
アカツキさま社内での OSS Gate ワークショップ開催は、ゴールではなく最初の一歩という位置付けです。そのため、毎回のワークショップ終了後にはアンケートの回答を見ながら参加者全員で「どうすれば、社内にOSS活動が根付くか?」という事を考えるふりかえりの時間を設けました。
ワークショップ後も継続してフィードバックしていく事が根付くかどうかが課題であるという点は、公開で開催されている OSS Gate ワークショップと共通している様子でした。アカツキさまのケースでは特に、「業務と並行してフィードバックも行う」という事を全体でどう習慣化していくかが課題という事になります。この点について、各回のアンケートの回答からは、現在は「普段の業務」と「OSS活動」を全く別の物として捉えている方が多いようで、「普段の業務をしていると、OSS活動をする時間がない」と認識されている様子がうかがえました。
業務上使用しているツールやライブラリの不具合を見つけた場合、仮にそれが自社開発の物であれば、不具合を伝えて修正してもらうという事は、業務の中の出来事として受け入れやすいでしょう。その際にはもちろん、修正する側にとってわかりやすい形の障害報告にするという事も求められるはずです。業務で使用するOSSへのフィードバックも、報告先が公開のイシュートラッカーであったり、報告に使う言語が英語であったり*8という差異はあるものの、基本的にはそれと同じ事と言えます。よって、心理的な障壁をどのようにして取り除くか、いかにして「業務の活動をしていたら、それが自然とOSS活動になっていた」と言える状況を作るか、という事が重要なのではないかと思われます。
ただ、業務での開発では「ライブラリやツールのバージョンは開発時点の物で固定し、最新版へは更新しない」といった事も行われがちです。その場合、フィードバックの結果が業務に反映されることが無いという事になってしまうので、開発元へのフィードバックに時間を使う事に対して「業務と無関係な事をしている」感覚が強くなってしまう、という側面はありそうです。この種の問題については個人でどうにもなりませんので、むしろ、古いバージョンのソフトウェアを使い続ける事にはセキュリティなどの面でリスクがあるという認識を全員で持って、適宜ソフトウェアのバージョンを上げていく健全な開発・メンテナンスのスタイルを取り入れていくという、普段の開発そのものの見直しも必要になってくるでしょう。
こういった事を全体で一気に推し進めるのは困難が伴います。アカツキさまでは元々、日常的にフィードバックをされている方の周囲ではスポット的にそのような動きが見られるとのことですので、全体を一気に改革するという事には拘らず、周囲の人に影響を与えるリーダーとなる人を増やした上で部分部分のできる所から進めていく、というのが無理のない進め方なのかもしれません。
また、この取り組み全体を担当されているシニアエンジニアの島村さんと一緒に行った、全体を通してのふりかえりの中では、OSSへのフィードバックで困った人が表れた時の気軽な相談先として当社をお使い頂く、というような方向でのOSS推進もご検討頂きました。
以上、アカツキさまでのOSS推進の取り組みの一環として実施した OSS Gate ワークショップの様子をご紹介しました。
当社では「自社内にOSS開発者を育てたい」「OSSへのコントリビュートを行いたい」といった企業さまのご依頼を受けて、人材育成や社内文化の改善といった観点からご協力させて頂くというOSS開発支援サービスを行っています。今回のアカツキさまでの事例のように、社内事情に合わせて規模や内容をカスタマイズしての OSS Gate ワークショップ開催のお手伝いも承っております。社内で旗振り役を務められる人が少ないためにOSS活動をうまく推進できていない、などのお悩みをお持ちの方がいらっしゃいましたら、是非メールフォームよりお気軽にお問い合わせ下さい。
なお、近い日程では2019年6月8日にOSS Gate 東京ワークショップが開催される予定です。公開のOSS Gateワークショップはサポーター側が不足しがちなため、ビギナー側での参加は枠がすぐ一杯になってキャンセル待ちになりやすいのですが、「バグ報告をした事がある」という方はサポーターとして登録して頂ければ、多くの場合はそのまま参加できます。会社としてOSS活動を奨励していきたいという方は、実際にワークショップに参加して頂くと雰囲気や要領を参考にして頂けると思いますので、是非ご検討下さい。
*1 詰まった時にヒントを出す、迷ったときにアドバイスするなど。
*2 東京開催分は、現在は基本的に1人のビギナーに1人以上のサポーターが付く形態とし、サポーターの人数以上のビギナーを集めない方針を取っています。アカツキさま社内での開催に関してはビギナー参加者を可能な限り多く受け入れる必要があったため、進行役・サポートメンターを加えればマンツーマンに相当する人数比は維持できそうという事から、この人数に設定しました。
*3 過去には、PHPカンファレンスの中で一室を借りて実施した際にもこの形態を取りました。
*4 Gitの使い方に手間取ったり、課題選びに悩んだり、選んだ課題の規模が大きすぎて準備に時間がかかったり、という例が多いようです。
*5 OSIでは、オープンソースの定義に合致していて、且つOSIで認定されているライセンスに基づくソフトウェアのみをOSSと呼ぶ事を推奨しています。また、よくある質問の回答として、オープンソースの定義に合致していても一覧に掲載されていない物をOSSライセンスと称する事は混乱を招くため、(承認され一覧に掲載されるまでは)「独自のOSSライセンス」を無闇に策定・自称しない事が求められています。なお、筆者がユニティちゃんライセンスの文面を確認した限りでは、営利目的での利用の禁止などの条件がオープンソースの定義に合致しないようでした。
*6 OSSでなくとも、ライセンスの条件に従って利用する限りにおいては何ら問題ありません。また、OSSライセンスでないライセンスを設定する事もここでは否定していません。ただ、OSSライセンス同士であっても条件が互いに矛盾するために成果物を組み合わせられない場合はあり、OSSライセンスとOSSでないライセンスとの組み合わせでは余計にそのような状況が発生しやすい、という事は言えます。
*7 s-luna/AndroidReplayRecorder
*8 作者が日本語話者のOSSでは、日本語でフィードバックするという事もありますが、多くの場合は英語が使われています。