Personal tools
You are here: Home ブログ 井上 WebSocketのプロトコル
« December 2010 »
Su Mo Tu We Th Fr Sa
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
Recent entries
Apache2.4のリリース予定は来年(2011年)初め(あくまで予定) inoue 2010-12-23
Herokuの発音 inoue 2010-12-20
雑誌記事「ソフトウェア・テストPRESS Vol.9」の原稿公開 inoue 2010-12-18
IPA未踏のニュース inoue 2010-12-15
労基法とチキンゲーム inoue 2010-12-06
フロントエンドエンジニア inoue 2010-12-03
ASCII.technologies誌にMapReduceの記事を書きました inoue 2010-11-25
技術評論社パーフェクトシリーズ絶賛発売中 inoue 2010-11-24
雑誌連載「Emacsのトラノマキ」の原稿(part8)公開 inoue 2010-11-22
RESTの当惑 inoue 2010-11-22
「プログラマのためのUXチートシート」を作りました inoue 2010-11-19
「ビューティフルコード」を読みました inoue 2010-11-16
Categories
カテゴリなし
 
Document Actions

WebSocketのプロトコル

世間から周回遅れですが事情によりWebSocketのことを調べてみました。

WebSocketの一次情報のリンクを挙げておきます。

WebSocketプロトコル

WebSocket API

まだプロトコルとして枯れていません。プロトコル仕様の日本語訳もありますが情報が古いので最新版と互換性がありません。この文書は2010年5月23日版の仕様を元にしています。情報がいつまで有効かは不明なので読むときは気をつけてください。

昔、なんでもHTTPという風潮が嫌いでした。通信プロトコルは適材適所であるべきだと思ったからです。HTTPに向いていない用途にまで無理矢理HTTPでラップするのが嫌いでした。いつしか、まあいいかHTTPで、と思うようになりました。文字コードはUTF8でいいか、と思い始めた時と同じ時期かもしれません。多様性は善というお題目を素直に信じられなくなった頃です。

とは言え、HTTPでプッシュ技術をするためにlong pollingをするのはいかがなものか、と思う程度の良識は持ち合わせています。別にlong pollingを全否定したいわけではありません。ハックとしてはありです。しかし、あくまでハックであって、表舞台で堂々と使う技法ではありません。

概要

  • クライアントからサーバにTCP接続してhandshakeする
  • handshakeのリクエスト/レスポンスのやりとりはHTTPそのもの
  • クライアントはリクエストボディで8バイトのランダム文字列(challenge)を送る
  • サーバは16バイトのresponse文字列を返す
  • handshake後、TCP接続は張りっぱなしにする
  • handshake後のTCP接続上、双方向にデータを送信可能
  • テキストデータは 0x00 で始まり 0xff で終了するフレーム単位
  • テキストデータの文字コードはUTF8
  • バイナリデータも送れる(タイプと長さを最初に送る)
  • URLは ws:// or wss://
  • 紳士的な切断は 0xff 0x00 を送って、0xff 0x00 を受信してからTCPレベルで切断

考察

TCP上の通信プロトコルの定石はTLV(type,length,value)の三つ組によるデータ送信です。

最初に型情報、次にデータ長、最後にデータを送ります。TCP接続を張ったままデータをだらだらと送り続けるプロトコルではこのスタイルが一般的です。データ長を最初に送ってその長さ分のデータを送るスタイルは受信側に優しいプロトコルです。最初にどのぐらいのデータを読めばいいかわかるのでプログラミング(特にメモリ確保)が簡単になります。読み取りの終端処理もデータの中身をパースすることなく行えます。ただし、送信側は、データの全長がわかるまで送信を開始できない欠点があります。ちなみに、HTTP1.1では、全長がわかる前にデータ送信したい要求のためにChunked Transfer Codingを使えます。

WebSocketの一般的なデータ送信は0x00で始まり0xffで終端するUTF8文字列です。一見、TLVっぽくありません。ただ、プロトコル上はTLVです。0x00はUTF8(0xff終端)のデータ型(frame type)を示すバイトという扱いです。先頭バイトのhigh-orderビットが立っている場合、後続に長さ情報が続くframe typeを意味します。high-orderビットが立っているバイトが続く限り、それらはデータ長を示します。データ長を示すバイト(high-orderビットを除く7ビットずつ)の終端はhigh-orderビットの立たないバイトです。データ長の後にはデータが続きます。受信側はデータ長だけバイト列を読み込みます。プロトコル上、ただのバイト列なのでバイナリデータも送れます。

通常用途では0x00(frame type)でUTF8文字列を送ることが多いと思います。文字列のフォーマットはJSONが良いでしょう。JSONであれば型情報も含めて構造データを送受信できるからです。

handshakeの具体例

GET /demo HTTP/1.1
Host: example.com
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Key1: 18x 6]8vM;54 *(5:  {   U1]8  z [  8
Sec-WebSocket-Key2: 1_ tx7X d  <  nw  334J702) 7]o}` 0
Sec-WebSocket-Protocol: sample                    ...アプリが使うことを想定したヘッダ
Origin: http://example.com

Tm[K T2u


HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Location: ws://example.com/demo
Sec-WebSocket-Protocol: sample

fQJ,fN/4F4!~K~MH

不思議なchallenge-response処理

handshakeリクエストヘッダの例

Sec-WebSocket-Key1: 18x 6]8vM;54 *(5:  {   U1]8  z [  8
Sec-WebSocket-Key2: 1_ tx7X d  <  nw  334J702) 7]o}` 0

ヘッダ値から数字部分のみ取り出し

1868545188
173347027

ヘッダ値の空白文字を数える

12
10

除算

155712099
173347027

16バイトデータ生成

ふたつの数値を32bit big-endianにエンコーディング

09 47 FA 63
0a 55 10 d3

リクエストボディの8バイトと連結して16バイト生成

Tm[K T2u
=> 54 6d 5b 4b 20 54 32 75
# 16バイトを書き込んだファイルのmd5値
$ md5sum /tmp/dat
66514a2c664e2f344634217e4b7e4d48  /tmp/dat

16バイトデータのMD5をresponseのボディで送る

fQJ,fN/4F4!~K~MH
# 確認
$ echo 'fQJ,fN/4F4!~K~MH'|od -t x1
0000000 66 51 4a 2c 66 4e 2f 34 46 34 21 7e 4b 7e 4d 48

これは一体何なんでしょうか?

普通のchallenge-responseは共有する秘密情報(普通はユーザのパスワード)を使って認証をします。WebSocketのchallenge-responseにそんなものはありません。処理はムダに複雑です(空白の数を数えるとか)。アルゴリズム的に計算量が爆発するという意味での複雑さには意味がありますが、単に計算が面倒という意味の複雑さはセキュリティ的な強度には無関係です。手作業の計算がいくら面倒だろうと、計算するコードを書いてしまえばおしまいだからです。

結局、これは何を守っているのでしょう。

The URL to Trackback this entry is:
http://dev.ariel-networks.com/Members/inoue/websocket/tbping

Re:WebSocketのプロトコル

Posted by naruse at 2010-08-11 11:44
仕様やMLの過去ログを読んでいる人がいないようなので紹介しつつ説明します。

参照なさった版は draft-ietf-hybi-thewebsocketprotocol-00 ですかね。
http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt
これは以前の WebSocket プロトコルから handshake 周りの互換性を壊したあとのバージョンです。

互換性を壊した時の Google 側のアナウンスは以下で、ここでは
"introduces nonce-based challenge-response to protect from cross protocol attacks"
と軽く変更理由に触れています。
http://blog.chromium.org/2010/06/websocket-protocol-updated.html

さて、この文書は 1.3. Opening handshake をご覧になりながら書かれたのだと思いますが、
そこには "This prevents an attacker from tricking a WebSocket server by sending it
carefully-crafted packets using |XMLHttpRequest| or a |form| submission."
とめんどくさい仕組みを入れた理由が書かれてあります。

また、スペースを入れたり2つに分けているのもそれぞれ理由があり、これまたきちんと理由が書かれています。
例えばスペースについては "_dividing_ by this number of spaces is intended to
make sure that even the most naive of implementations will check for
spaces, since if ther server does not verify that there are some
spaces, the server will try to divide by zero, which is usually fatal
(a correct handshake will always have at least one space).
つまるところ、きちんと仕様を読まない人でも安全に実装できるように考慮されているという話ですね。
前後を読むと具体的に想定しているのが HTTP ヘッダの GET/POST 行の PATH 部分からの攻撃であり、
PATH に含むことのできない文字であるスペースを意味のある文字列として入れることで回避しています。

まぁ、この仕様だけから読み取るのは難しい気もするので、MLでの議論も参照します。
HTML5関連の仕様はwhatwgやw3cやIETFなどで作っており、議論も公開されています。
で、WebSocketの場合はIETFで議論を行っています。
https://www.ietf.org/mailman/listinfo/hybi

この handshake 周りの変更に関する議論は、
http://www.ietf.org/mail-archive/web/hybi/current/msg01268.html のスレッドから始まり、
http://www.ietf.org/mail-archive/web/hybi/current/msg01309.html にて
現在のスペースを用いた手法の紹介とその理由が説明されています。
簡単にいうと、適当に正規表現で抜き出して来る実装を不可能にすることによって、
そういった手抜き実装に対してXMLHTTPRequestやformから攻撃できないようにするということです。
割り算にしているのはクライアント側が手抜きをしてスペースを入れないようにすると、
ゼロ除算エラーがでて動かないようにするためとか、紹介した以外の部分では既存のHTTPクライアント/サーバーに
送った場合の互換性を考慮していたりだとか、まぁ細かい所まで気を使っていて泣けます。

HTML5は以上の2つのメールの送信者であるIan Hicksonが主導しているんですが、
この手の気遣いが随所に溢れていて、これこそが当初は中二病プロジェクトだと思われていたHTML5が、
お、なかなかやるじゃないか、という昨今の評価につながっているんだろうなぁと思ったりします。

というわけで、今回のWebSocketのhandshake部分以外でも、何かよりよいアイディアや、
既存実装との互換性において不都合のある点、セキュリティ上の懸念等があれば、
それぞれのMLにフィードバックを送ってあげるととてもよいことでしょう。

Re:WebSocketのプロトコル

Posted by inoue at 2010-08-12 00:35
MLの過去ログ等はまったく追えていません。参照先の提示、ありがとうございます。

とは言え、実は、"This prevents an attacker from tricking a WebSocket server by sending it carefully-crafted packets using |XMLHttpRequest| or a |form| submission."と見ても、何から何を守ろうとしているかわからない、が率直な感想です。

教えてくださいと言うのも厚かましいので、参照先の情報を見てみます。

Add comment

You can add a comment by filling out the form below. Plain text formatting.

(Required)
(Required)
(Required)
This helps us prevent automated spamming.
Captcha Image


Copyright(C) 2001 - 2006 Ariel Networks, Inc. All rights reserved.