JavaScriptからハードウェアを扱えるWebAPIとその安全性について

最近話題に上がったWebAPIとその安全性について、少しお話しようと思います。


# はじめに
私は、他のどの言語よりJavaScriptが好きです。
ウェブブラウザとテキストエディタさえあれば簡単に始められ、スクリプト言語なのに超高速で、日々進化しており、実行環境や用途を選ばないからです。

そして特に、それらを最先端で支えるChromium(Blink/V8)コミュニティの、新しい実装や活動へ積極的に取り組む姿勢は、とても素晴らしいと感じています。
おかげで、昔はインストールしなければ使えなかったアプリケーションも、今では"Web版"の文字を見る機会も増えました。

最近では [WebUSBを使ったAndroidのアップデート](https://source.android.com/setup/contribute/flash) が話題になりましたね。

そこへ "ちょっと待った" がQiitaユーザーの方から出たわけですが、個人的には少し納得のいかない内容だったため、こうして記事にしてみました。


# WebAPI
ここで言うWebAPIとは **ウェブブラウザからハードウェアを扱えるAPI** のことを指します。

私が扱ったものや調べたものでは、以下のハードウェアを制御できます。

- USB
- SerialPort
- BluetoothLowEnergy
- NFC
- GPS
- MIDI
- カメラ
- マイク
- HID
- VR/ARデバイス
- 加速度センサー
- 環境光センサー
- バイブレーション
- バッテリー
- Wi-Fi
- ファイルシステム

などなど、まだ他にもあると思います。


# 攻撃と耐性
成長や人口増加は嬉しい反面、それに比例してセキュリティという大きな問題に悩まされるのも事実で、閲覧者を狙う攻撃手法も日々巧妙となっています。

ウェブサイトの安全性と言われて、皆さんがまず最初に思い浮かべるのはXSSやXSRFだと思います。
実際にそれらの攻撃は非常に身近であり、日常にも数多く潜んでいます。

スクリプト攻撃の代表格であるXSSやXSRFの詳細は、諸先輩方が丁寧に解説してくださっているため割愛しますが、おさらいすると **攻撃者により仕込まれた悪意のあるスクリプトが閲覧者に気付かれず実行される** というものです。

悪意のあるスクリプトとは、データを改竄したり奪取したり、それはもう色々です。
(厳密にはXSRFは意図しない"リクエスト"を発行させる攻撃ですが、スクリプトによる非同期通信で簡単に実装できてしまうため、ここでは同じ扱いとします。)

ウェブブラウザでハードウェアを扱えるということは、それらの攻撃によってハードウェア情報や、更にはハードウェアを使用する人の情報(個人情報)も狙われる危険性がある、と言えます。

しかし、当然それらの攻撃はWebAPIの検討段階で事前に想定されており、しっかりと対策も施されています。
その対策とは、大きく分けて3つ存在します。

- WebAPIが呼び出されたときは、ユーザーへ許可を求めます。
- 許可しなかった場合は、実行されることはありません。
- 許可の可否は、スクリプトからは指定できません。

また、WebAPIだけでなくJavaScript全体としての実行環境も、様々な安全対策が盛り込まれています。

- 完全に分離されたスレッド単位のインスタンスを生成するため、実行中のプロセス空間において安全性が確保されます。(V8の実装は"Isolate"と言い、SpiderMonkeyの実装は"JSRuntime"と言います)
- 高精度タイマーAPIは、サイドチャネル攻撃に利用される危険性を防ぐため、結果をミリ秒単位へ丸める "ボカし" が入っています。

これはほんの一例です。(流石に追いきれない🥺)


# 実行の可否
では、どのような状況で許可を求めてくるのでしょうか。
また、悪意のあるスクリプトへ対する挙動はどのようになるのでしょうか。

WebAPIの許可周りは、基本的にどれも同じような作りとなっています。
おそらく **統一することで見通しを良くして攻撃のスキを与えない** という考え方なのだろうと思います。

実際にWebAPIのひとつであるシリアルポートを使用して、流れを見てみましょう。
なお、WebAPIは基本的に`Promise`インターフェイスで提供されます。

## 実行される例
最初に、正常に実行されるパターンです。

```html
<html>
    <body>
        <button id="connect">Connect!</button>
    </body>

    <script>
        const button = document.getElementById("connect");

        button.addEventListener("click", async()=>{
            const port = await navigator.serial.requestPort();
        });
    </script>
</html>
```
![request.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253070/c87e0bb1-ae8a-1a5f-c165-ee6089197cf9.png)

このようなページを書いた場合 **ユーザーによりボタンが押された場合のみ** 許可を求めるウィンドウが表示されます。
このウィンドウは、ユーザーがボタンを押す以外には制御できず、スクリプトから制御可能なインターフェイスは提供されません。

デバイスが検出された場合は、デバイスを選択して "接続" を **押すと** デバイスのポートを取得できます。
デバイスのポートを取得した後は、各WebAPIの仕様に沿いデータをやり取りできます。

## 実行されない例
次に、悪意のある使用が想定されるため、仕様として実行が許可されないパターンです。

```html
<html>
    <body>
        <button id="connect!">Connect!</button>
    </body>

    <script>
        const button = document.getElementById("connect");

        button.addEventListener("click", async()=>{
            await navigator.serial.requestPort();
        });

        // 1.ページロードによる実行
        navigator.serial.requestPort();

        // 2.JavaScriptによる発火
        button.click();

        // 3.非暗号化接続(一部)
        console.log(location.protocol); // => "http:"
        // 注: シリアルポートは非暗号化接続でも使用可能
    </script>
</html>
```
基本的に、自動的に実行されようとしたときは、許可を求めるウィンドウすら出ずに拒否されます。

また、カメラやマイクなどのネットワークを介した利用が想定される一部のWebAPIは、ウェブページがローカルファイルまたは暗号化接続でない場合は、WebAPIのコンテキスト自体がそもそも提供されません。

そして、いずれかの理由で拒否された場合は、以下のようなエラーメッセージがログへ流れ、その時点でスクリプトは強制終了されます。

つまり、XSSやXSRFなどによる **気付かれないままの実行や許可は不可能** と言えます。

![ERROR1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253070/5d5dce99-3d25-1535-289f-add289795c70.png)


# WebAPIの課題
上記のとおり、HTTP接続時に使用不可となるWebAPIは一部のため、これを全てのWebAPIへ適用するべきだと考えます。

たとえデバイスから得たデータをネットワークへ介さずローカルで完結させるウェブページ(いわゆるSPA)でも、安全性でいえば暗号化されているに越したことはありません。

Chromiumでは、FTP接続のデフォルト無効化やHTTPS/HTTP混合コンテンツのHTTPコンテンツ無効化など、非暗号化に対する警戒を高めているので、この具合で対応してもらえれば、安全性は更に向上するでしょう。


# 知らないものは怖い
ウェブブラウザの近年の成長ぶりは著しく、ここ数年で本当に様々なことができるようになりました。

しかし、その一方で昔ながらの考えを持つ方も一定数いらっしゃるようです。
以前、おそらくバックエンドで長年お仕事されていたであろう方のブログで、フロントエンドを批評した記事へのコメントで以下のようなものがありました。

「主さんは長年に渡りバックエンドへ浸かっており、フロントエンドは表示させるだけのただの受け皿という伝統を守りたく、フロントエンドにはそこはかとない不信感を抱いているように見えます。」
(こんな感じだったと思う...意訳)

なるほど、そういうことか。
これが全てではないとは思いますが、実際このような方も多いのではないかと思いました。

人間は知らないものを怖いと感じる生き物です。
それは仕方のないことです。
私にだってそういうものはたくさんあります。

ですが、より便利に成長しようとしている芽をつぐんでしまうのは、あまりにもったいないと感じます。
この記事は、そういった方々の理解を得るのは難しいかもしれませんが "名前は聞いたことあるけど何となくやばそう" みたいなぼんやりとしたイメージを持たれている方へ、ちゃんと安全性は確保されてるのだと伝えたくて書きました。


# 気を付けるべきこと
![yoshi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253070/26cb805f-be17-7d66-d6a6-91818412cec9.png)

正直、これらのように仕様側でいくら安全策を取ったところで、利用側が **許可？よく分かんないけどヨシ!!** では、何の意味もありません。

しかし、だからと言ってそれが **ウェブブラウザの成長を止めていい理由にはならない** と私は考えています。
何故ならそれは(実装上の不備がない限り)ウェブブラウザが抱えるべき問題ではないからです。

最近ではAndroidやiOSなどのスマートフォン向けプラットフォームにおいても、パーミッション機能はかなり厳密化されてきています。
更には、今まであまり見向きされてこなかったWindowsプラットフォームにおいても、厳密化の動きがあります。

しかし "ヨシ!!" な人はプラットフォームが何であれ、何でもかんでも許可するものはしてしまいますし、許可されたプラットフォーム側はそれを本人の意思だと認定し、たとえ悪意のあるコードでも忠実に実行してしまいます。

そしてそれは、充分に自己責任の範疇にあたると思います。

インターネットが発達し、これからもウェブブラウザはどんどん進化を遂げ便利になっていくでしょう。

そのためには、不必要な許可連打や本来の用途と無関係な許可要求への注意など、プラットフォームの分類に囚われず、いまいちど **アプリケーションへの接しかた** を見直すべきではないでしょうか。