nest: セキュリティプロトコルの構想
https ←→ http の通信自体は可能っぽいので、セキュリティを担保するためのセキュリティプロトコルの設計を考えたい。
nest / nestdevice とは
nest(サイト)は https でホストされる 静的な サイトで、ローカルネットワーク上のデバイスに対してWebRTCリンクを確立することでローカルネットワーク上のリソースを活用したアプリケーションを実現する。
nestサイトは以下のような機能を持つ:
- WebApp: サービスワーカーにより任意のサブディレクトリにローカルのWebアプリを "マウント" する。
- WasmApp: パブリックなインターネット、または、ローカルに存在するWasmアプリを実行する。
WebAppは実験用で、たぶん世間的に重要なのはWasmAppの方だろう。
nestdeviceはnestに機能を提供するデバイスで、ユーザーのローカルネットワーク上に存在する(インターネットからは到達できない)。nestdeviceは (Secureでない) シグナリング専用のHTTPサーバーを提供し、nestサイトを表示しているユーザーのブラウザとの間にWebRTC接続を確立し、サービスを提供する。
- Web API proxy: WebAppのためのhttp forward proxy
- 静的ストレージ: nest上で動作するWebAppやWasmアプリ向けに自由に使用できるストレージを提供する。
- ネットワーク: nest上で動作するWasmアプリ向けに自由に使用できるネットワークを提供する。
... 昔だったらブラウザプラグインで実現してそうな機能性だな! 特に、 ユーザーはWebAppを提供するnestdeviceを絶対的に信用する 。WebAppはnestサイトのoriginで実行されるため、進行中のWebRTC接続を横取りするといった処理が可能になっている。
nestdeviceのペアリング
nestdeviceはnestサイトとペアリングしてから使用する。デバイスキーはbase64uriエンコードされたJSONで、シグナリング用のURLと EC
P-256
公開鍵を含んでいる。
-
u
: シグナリング用URIプレフィックス。スラッシュで 終わらない 。 -
k
: 公開鍵として使用するJWKオブジェクト
デバイスキーは究極的に信頼できる方法で受けとり、入力する必要がある。
ユーザーがデバイスキーをnestサイトに入力し、"信頼"すると、nestサイトの localStorage にデバイスキーが保存される。
ksy0
: 追加鍵の受信
JWEの標準では EC
では暗号化を行えないため、RSA暗号公開鍵を受信する。基本的に接続のたびにこれを行う必要がある。
(送信データ)
- (特になし)
(受信データ)
- 公開鍵のJWKオブジェクトをJWSしたもの
受信したデータは常にデバイスキー内の公開鍵で検証される。
con
: WebRTC シグナリング
シグナリングの通信は信頼できない通信路で行われるため、暗号学的な保護を必要とする。今回の場合、
- nestdevice → nestサイト: EC公開鍵で 署名 したJWS
- nestサイト → nestdevice: RSA公開鍵で 暗号化 したJWE
で通信する。バリデーションに失敗した場合は接続を無視する。 FIXME: 双方向に暗号化しないとMITMが成立する気がするけど、nestdeviceの署名鍵を破らないで達成する方法があるか。。?
エラーが発生した場合は err
をレスポンスに含める。
Phase1とPhase2の間は10秒以内に完了しなければならない。
(session情報なし)
Phase1: - request(nestサイト)
-
req
:"new-connection"
-
- response(nestdevice)
-
ident
: 接続のUUID (wait-peer-ice) -
d
: SDPデータ -
c
: canditate配列 (c
: candidatem
: mid )
-
"wait-peer-ice"
Phase2: - request(nestサイト)
-
req
: "peer-ice" -
s
: SDPデータ -
ident
: 接続のUUID
-
- response(nestdevice)
-
res
:"done"
-
ident
: 接続のUUID
-
nestdeviceのワイヤプロトコル
nestdeviceにはワイヤプロトコルが必要となる。
hash部分はネットワークには乗らないため、一旦HTTP経由で静的ページをserveして、別のエンドポイントに対して fetch
APIで POST
することになる。
この部分の通信路は信頼できないため、 POST する body やレスポンスはJWS/JWEのcompact表現文字列となる。内容の検証は(HTTPSでserveされる)nestサイト側で行わなければならない。
- nestサイトは
https://nestsite/cnt/talk.html#<session-data>
を新規ウィンドウで開く -
talk.html
は sessionStorageを監視し、送信すべき<data>
と<func>
を待つ - 送信対象が到着したら、
window.location.href
をhttp://nestdevice/cnt/talk.html#<target-data>
に変更する -
<target-data>
から<func>
と<data>
を抽出し、POST
http://nestdevice/<func>
←<data>
→<response>
-
talk.html
はwindow.location.href
をhttps://nestsite/cnt/talk.html#<response-data>
に変更 - (2に戻る)
<session-data>
はURLsafe base64データで、以下のデータをJSON形式で持つ:
-
ses
: sessionStorageで使われるセッション名 ↑のWebRTCシグナリングで使われるUUIDとは異なる
<target-data>
はURLsafe base64データで、以下のデータをJSON形式で持つ:
-
cb
: nestサイトのURLプレフィックス スラッシュを含まない -
f
:<func>
-
d
:<data>
。ただし base64デコードされている ため、送出前に再度エンコードする必要がある -
ses
:<response>
に設定するセッション名
<response-data>
は URLsafe base64データで、以下のデータをJSON形式で持つ:
-
r
:<response>
。ただし base64デコードされている ため、送出前に再度エンコードする必要がある -
ses
: セッション名 -
err
: エラー用
エラーがあった場合のみ err
を含める。
localStorage
nestサイト内での通信は localStorage を使用する。
-
nestdev.sessions
: セッション名(ses
)一覧の配列を持つJSON文字列。 -
nestdev.<ses>.in
: セッションのメッセージ番号を表わすJSON文字列。 -
nestdev.<ses>.inbox
: nestサイト → nestdevice データを表わすJSON文字列。 -
nestdev.<ses>.out
: セッションのメッセージ番号を表わすJSON文字列。 -
nestdev.<ses>.outbox
: nestdevice → nestサイトデータを表わすJSON文字列。 -
nestdev.<ses>.cb
: 呼び出し先 (JSONではない)
in/inbox、out/outboxの各ペアはメッセージの送受信に使用する。inbox/outboxは以下のデータが格納される:
-
cnt
: カウンタデータ -
data
: 送信対象のデータ -
func
: (inboxのみ) 送信対象のAPI名
通信時は、常に inbox → in の順で更新し、 inbox.cnt
== in
となるのを待ち合わせることになる。
エラーやその他の理由で通信を終了したい場合はinに false
をセットする。