node-bitmap-proxyは、機密情報を扱うことはできるか?
完全に雑記帳です。
一般のweb APIをScrapbox記事内から実行することを考えてみる
そのような場合に必要になってくるのが、機密情報の管理である。
現状の回避手段では、画像URLに全ての情報を詰め込まなければならない。しかし画像のURLは簡単に傍受されてしまうから、URLに直接機密情報を記載することはできない。connect-srcも厳しく制限されているため、事前に安全な手法でPostリクエストを送っておいて、識別子とともに画像データをリクエストする、みたいな回避策も難しい。そこで、機密情報を適切に暗号化し、安全な通信を確立する必要がある。
要件を確認しておく。今回は、以下の流れを考慮する。
画像URLをプロキシサーバーに送信する
プロキシサーバーがURLに含まれる機密情報を取得し、外部サーバーから情報を取得する
プロキシサーバーが取得したデータ・メタデータを画像として返送する
画像データから情報を読み出す
今回は、サーバーと外部サーバーの通信は安全なものと仮定する。また、プロキシサーバーは各自が自前で用意するものとし、それを利用する限りで情報漏洩の危険はないとする。
その場合、対処すべきものは以下の事態であると思われる
画像URLからの機密情報の盗聴
画像URLの改ざん
画像データからの機密情報の盗聴
画像データの改ざん
サーバーのなりすまし
ユーザー側のなりすましの防止は、自前のサーバー運営費や利用割り当てを他人に浪費されないためには重要であるが、今回は扱わないものとする。
と、ここで「そういえば、通常のリクエストはSSL/TSL通信によって安全性が担保されているよな。そうであれば、通常のSSLと同等の仕組みをクエリパラメータに導入すれば、問題は解決するのでは?」と気づいた。
そこで調べてみたところ、こんなのが書いてあった。
なるほど、クエリストリングも暗号化されていて、傍受の危険はないらしい。しかし、この記事の質問の返信でもあるように、アドレスはさまざまなところに保管されるため、素文をクエリストリングで送信することには慎重になった方が良さそうだ。IPA ISECにも解説記事が載っている。大前提として、「クエリストリングには見えてもいい情報だけを載せる」という前提で、さまざまなソフトウェアが構築されていることがわかる。 というわけで、問題は通信路防護だけではない。様々なソフトウェアに保存されることを前提に、セキュアな文字列を作成する必要がある。もちろん、共通鍵暗号方式で、各自でパスフレーズを作成、ということにしてもいいのだけれど、今回はそれをシェルスクリプトで書いておいて、導入手続きとしてリポジトリに掲載しておこうと思う。
流れを確認しておく。
まず、共通鍵を作成し、安全な方法でそれをプロキシサーバーに保存する。
ScrapboxのUserScriptには、あらかじめ共通鍵で暗号化した機密情報を記載する。
機密情報を含む本来のリクエスト内容と、復号すべき項目のリストを合わせて文字列データを作る。
この文字列を圧縮し、クエリパラメタとして画像URLに記載(ブラウザがGetリクエストを投げる)
プロキシサーバーは、機密情報を解読し、リクエストを復元して本来のリクエストを送信
戻ってきたデータにメタ情報を加え、再び共通鍵で暗号化し、画像にエンコードしてブラウザに返却
画像からデータを取得
ここまで書いて気づいたが、共通鍵暗号方式のみでは、返却データの復号ができない。どうしてもScrapboxに鍵を記載する羽目になるからだ。逆に画像には機密情報を書かないとか、画像に潜ませるなら随分と安全と仮定して素文でやってしまうか。いや、一時パスワードがいいかもしれない。そうだ、閃いた。
ScrapboxのUserScriptに、RSA 公開鍵暗号を生成させるスクリプトを書いておこう。おっと、そう言えばScrapboxはcdnjs.cloudflare.comの利用を許可しているな。調べてみると、 有名そうな暗号生成器もcloudflareに公開されているじゃないか。これは都合がいい。githubの長大なコードをscrapboxに転記する手間がなくなった。これを使えば、上の流れは以下のようになる。 事前に共通鍵を作成し、安全な方法でそれをプロキシサーバーに保存しておく。
ScrapboxのUserScriptには、共通鍵で事前に暗号化した機密情報を含むリクエストを記載する。
UserScriptが実行時に、一時パスワードとして、RSAの公開鍵方式の暗号ペアを作成する。
事前に暗号化した機密情報を含むリクエスト内容と、そのうち復号すべき項目名のリスト、作成した公開鍵の三つを合わせて文字列データを作る。
この文字列を圧縮し、クエリパラメタとして画像URLに記載(ブラウザがGetリクエストを投げる)
プロキシサーバーは、機密情報を解読し、リクエストを復元して本来のリクエストを送信
戻ってきたデータにメタ情報を加え、公開鍵で暗号化し、画像にエンコードしてブラウザに返却
UserScriptがRSA秘密鍵を使って、画像からデータを取得
一連の通信が終了したのち、一時パスワードは記録せず破棄する。
Scrapboxに記載する必要のあるものは以下。
RSA暗号ペアの生成スクリプト
共通鍵で暗号化した機密情報を含む、リクエスト情報
リクエスト情報のうち、暗号化されている項目名のリスト
RSA復号スクリプト
上記4点を適切に利用する、オリジナルのfetchスクリプト(オプション)
サーバーで準備すべきものは以下。
共通鍵暗号
プロキシサーバー
画像エンコーダ(作成済)
公開鍵暗号スクリプト
ひとまず作り始めて全然良さそうだな。
いやいや。作り始めて気づいたけど、ユーザーのなりすましを妨害しないとやばいじゃないか。API Keyそのものは確かに見えてないけど、基本的にはUserScriptでサーバーURLも何もかも公開してるんだから、ソースコードのコピペをすれば同じAPIを使えてしまう。それはやばい。うーむ、本当にScrapboxからパスワードを聞くのか?それはまずい。なんかめちゃくちゃ怪しそうだ。それに手間に感じる。ユーザー自身を信頼性を持って識別するもの…。
そうだ。Scrapbox自体のログイン情報を使えばいいんじゃないか?きっとクッキーとかにセッション情報が残ってるだろ。
クッキーには残ってなかった。localStorageにJSON.parse(localStorage.getItem("userScriptSHA1"))をやってみるとなんかそれっぽいのが出てきたけど、これはuserScriptの識別情報っぽいな?
そしてsessionStorageにはなしと。。。
ネットワーク見てみたら、やっぱりクッキーにはhttpOnlyってあったわー。そりゃそうや。これだと、UserScriptだけから信頼に足る個人識別をやるのはしんどそうやな。
調べてみると、/scrapboxlab/api/users/meというAPIが公開されているらしく、試してみたところ、なるほどcsrf tokenというのが帰ってきた。これは確かにユーザーからしかえられない秘匿情報だとは思う。しかしこれどうせアクセスし直したらすぐに変わるんじゃないの... api/users/meで帰ってきたデータは一応残しておく。データ見る限り、ここに書いてある内容の真偽を細かく検証する、というのはおそらく簡単にできる。しかしそれはいくらでも偽造ができる項目なわけで、作業量さえかければいくらでもアクセスできるというのはよくない。 検証したけど、うん、CSRF Tokenはやっぱりすぐ変わってますね〜。サーバー側から検証もできないよねこれ。うーん。手詰まりかな、流石に。
Scrapboxはなかったけど、GyazoのClient idが載ってる!あとはこれを検証できればいいわけだ。サーバーは信頼できるって前提だからね。
いや、gyazoのclient keyはそこまで重要な情報じゃないっぽいぞ。あくまでログインを促すことができるぐらいか。考えれば考えるほど、余計なことを考えずに、その場でログインさせるのが一番良さそうに感じてきた。他の認証サービスにJWTログインさせて、そのJWTを見るのが手っ取り早くていいよね。あるいはユーザー認証を実装してしまうか。
あっ、せっかく画像で交信させてるんだから、こっちからもGyazoで通信しちゃえばいいじゃん!!!
Gyazoに画像アップロードして、そのURLだけ通信すればいいっしょ!!
これで認証問題も解決だし、何よりPostが実装できて一石二鳥だね!