巨象の肩に乗ってみたかった

「巨象の肩に乗って」という小説があります。作中でTwitterが中国へ進出したことを知った主人公が友人とMastodonを改造し、ユーザーに安全な通信手段を提供するという、痛快な物語です。

> 僕が考えたのは、三年前に立ち上げておいた〈マストドン〉というツイッターのクローンを、強い暗号で鎧ってしまうことだった。
>
> (中略)
>
> これで、僕のインスタンスの住人が交わす非公開のメッセージは、警察にも、諜報機関にも、そしてインスタンスの管理者である僕にも読めなくなる。

「巨象の肩に乗って」『[ハロー・ワールド](https://www.amazon.co.jp/dp/4065133084)』藤井 太洋著、講談社、ISBN 978-4065133088、2018年より

かっこいい！僕もこういう機能を追加できないかな？

## ブラウザ上で暗号化・復号してみる

この物語では、上に引用したように、Mastodonに、インスタンスの管理者にも解読できない暗号化した通信機能が追加されます。その他、ブロックチェインを利用したユーザーの身元の保証、ローカルストレージを利用したインスタンスの引っ越し手段も追加されますが、ここでは、暗号化したメッセージのやりとりについて考えてみます。

暗号化・復号にはデファクトスタンダードになっている[PGP](https://ja.wikipedia.org/wiki/Pretty_Good_Privacy)を使えば良さそうです。PGPでは暗号化に受取人の公開鍵を、復号や署名に自分の秘密鍵(私有鍵)を使います。複数の公開鍵を使って複数の宛先にむけて同時に暗号化することもできます。インスタンス管理者がユーザーのメッセージを復号できないためには、私有鍵はインスタンス以外の場所に保管する必要がありそうです。また、平文のメッセージがインスタンスに送信されないようにする必要もありそうです。

つまり、インスタンンス管理者が復号できないために、

- 暗号化・復号にはPGPを使う
- 鍵対はインスタンス外で管理する。アカウント登録時にMastodonが生成するものは使わない
- ユーザーのウェブブラウザ上で暗号化や復号をおこなう

ことにしましょう。

## JavaScriptでの実装

[Mastodon](https://joinmastodon.org/)は[Ruby on Rails](https://rubyonrails.org/)を利用して構築されたアプリケーションで、投稿をするためのユーザーインターフェースは、[React](https://reactjs.org/)+[Redux](https://redux.js.org/)で書かれています。今回はウェブブラウザ上で暗号化や復号をおこなうので、React+Reduxに手を加えることになります。いやあ、泥縄式に勉強しましたよぉ。

そうしてできたのがこちら。投稿内容をタイプして、公開鍵を今回は[Keybase.io](https://keybase.io/)から取得してくるようにしました。投稿ボタンを押すとウェウブラウザ上で[openpgpg.js](https://openpgpjs.org/)を利用して暗号化した内容が投稿されます。(「ハローワールド」では非公開の投稿が暗号化されますが、この例では公開のものも暗号化されています。)

![mastodon-oxpecker.gif](https://qiita-image-store.s3.amazonaws.com/0/105088/4347907f-e559-3265-4c14-3ae561c2750a.gif)

ソースコードは、[github/zunda/mastodon#try-oxpecker](https://github.com/zunda/mastodon/tree/try-oxpecker)で参照できます。2018年12月9日ごろの[自分のMastodonインスタンス](https://mastodon.zunda.ninja/)のコードからブランチしてmacOSでローカルに動かしやすいように[cld3への依存を取り除いたら](https://github.com/zunda/mastodon/commit/046335320989a1d50d53174059da87d6b1cdd638)、ひたすら新機能を追加していきました。

それにしても、ウェブブラウザだけで、

1. 公開鍵を取得し
2. 投稿内容を暗号化して、
3. 投稿している

のを見るのはなかなか壮観です。

## でも、ちょっと怖いよね
しかし、実装しているといろいろ怖いことに気づきます。安全に鍵対を管理することはできるでしょうか？

例えば、インスタンス管理者はユーザーに知られずに自分の公開鍵を投稿の宛先に含めることができそう。[下記](https://github.com/zunda/mastodon/blob/2914940/app/javascript/mastodon/actions/compose.js#L183-L191)は投稿ボタンが押された時にブラウザ上で投稿内容(`status`)を宛先の公開鍵(`pubkeys`のうち`active`で`valid`なもの。Reactの描画時の冗長なコピーを防ぐために鍵のデータそのものは`pubKeyStore`に格納してあります)のために暗号化するコードです。このコードはブラウザがインスタンスから取得するので、インスタンス管理者がこっそりここに自分の公開鍵を追加して暗号を復号できるようにしておくことができます。それを検出するのはなかなか難しそう。

```js:actions/compose.js

openpgp.encrypt({
  message: openpgp.message.fromText(status),
  publicKeys:
    pubkeys
    .filter(k => k.active && k.valid)
    .map(k => pubKeyStore[k.fp])
    .reduce((acc, cur) => acc.concat(cur), []),
})
.then(cipherText => {
  // 暗号文を投稿するコード
})
```

いっぽう、自分あてかもしれない暗号化された投稿を復号するには私有鍵が必要ですが、公開鍵の追加と同様、ブラウザがインスタンスから取得してくる実装が、私有鍵を盗み取っていないか確認するのは難しそうです。

## 管理者を信用しないでも安全に情報をやりとりするには

今回の実装では暗号化をインスタンス管理者の管理下にあるコードでおこなっているため、ユーザーのウェブブラウザ上で暗号化や復号をおこなったとしても、平文の内容がインスタンス管理者に漏れてしまう可能性があることがわかってしまいました。

これを防ぐには、ウェブブラウザ本体やコマンドラインなどで、ローカルにな実装で鍵対の管理と暗号化・復号をしてしまう必要がありそうです。例えばLinuxでは`gpg`コマンドが使えますが暗号化・復号の手順はそれほど単純じゃありません。多くのMastodonユーザーに使ってもらうのは難しそうです。ウェブブラウザのAPIを通してユーザーが安全に暗号化・復号をできるようになってるといいんだけどな。

とりあえずは、ブラウザ拡張を実装してテキストエリア内の平文を暗号化するなどすれば、インスタンス管理者は信用しないでも、ブラウザ拡張を信用できれば安全に情報をやりとりできるようになりそうです。そのうち実装してみたいな。

いずれにせよ「巨象の肩に乗って」を読んだおかげで現代の暗号技術を再訪することができました。手元で実際に動くのはいい。楽しかった！『[ハロー・ワールド](https://www.amazon.co.jp/dp/4065133084)』には、この他にも技術好きが読んで楽しい近未来SF短編が収められています。未読の方はぜひ読んでみてください！

### MastodonのE2EE API (追補)
2020年7月に、Mastodonに、[ダイレクトメッセージをエンド・トゥー・エンドで暗号化(E2EE)するAPI](https://github.com/tootsuite/mastodon/pull/13820)がマージされました。この機能を利用すると、Mastodonのクライアントデバイスどうしが、Mastodonのサーバを介して、しかしMastodonのサーバには漏洩しないように、暗号鍵を交換し、暗号化したダイレクトメッセージのやりとりをすることができます。その後、対応するクライアントソフトウェアが存在しないこともあり、[この機能は一時的に無効化されました](https://github.com/tootsuite/mastodon/pull/14283)が、興味のある人はクライントソフトウェアを実装してみると楽しそうです。

## (蛇足) 素振りの重要性

必要な時に素早く動けるよう、手に馴染んだ技術を持っておきたいものです。

「巨象の肩に乗って」の作中では機能のリリースまで着手から12時間前後でした。物語は2020年3月16日から始まります。この日の早朝、作中でのTwitterの変節を知った主人公がMastodonでのやりとりを暗号化する作業を始めたのがその日の夜、知人からのPRが送られてきたのが3月17日の午前3-4時、そして、その知人による作業を経て暗号化したインスタンスが稼働を始めたのが午前7時すぎでした。

いっぽう、何年か前にReact風なアプリケーションを書いた経験があるだけの僕の実装には、28日かかりました。2018年11月19日ごろからコードを書き始め、翌日の[Reactのみでの書き捨ての実装](https://gitlab.com/zunda/encryptor-on-browser)の[リリース](https://mastodon.zunda.ninja/@zundan/101108334225283105)と、12月4日の[Reduxも含めた書き捨ての実装](https://gitlab.com/zunda/react-redux-pgp)の[リリース](https://mastodon.zunda.ninja/@zundan/101187311279171340)を経て、Mastodonに機能を組み込むことができたのは12月16日になってからでした。Mastodonへの機能追加中にブランチを1つ捨てているので、ここまでで4つの実装を書いて、やっと、Mastodonへ機能を追加できたことになります。

この違いは大きいなあ。いざという時のために(どんな？)、素振りは続けておきたいものです。

この記事は[Mastodon Advent Calendar 2018](https://qiita.com/advent-calendar/2018/mastodon)の22日分としてお届けしました。昨日はS_H_さんによる「[MastodonInstanceBoosterを作ってみた](https://gamelinks007.hatenablog.com/entry/2018/12/21/000000)」でした。知らないインスタンス、いっぱいあるよね。活用してみたい (とは言え弊インスタンスには紹介するほどのことはないのでありました)。