WebSocketの仕様を調べていたら頭がどうにかなりそうだった。

  • Posted on
  • by

WebSocketを使ってゴニョゴニョしているのだが、handshakeの失敗時のエラーをどう扱うべきなのか、よくわからない。WebSocket Protocol の仕様と WebSocket API の仕様で書いてあることが違う。
protocol仕様を読む限り、サーバーはhandshakeを拒否する場合適切なHTTPエラーコードを返さなくてはいけない、と書いてある。が、クライアント側のウェブブラウザ上のjavascriptでそれを期待しているとエラーイベントからはHTTPエラーコードは受け取れない。WebSocketの異常終了コード1006が固定でセットされている。

そこでAPI仕様を読むと、これらを行わない理由が赤字で大きく書いてある。

  • オープンリダイレクタが設置されたサイトで脆弱性になるので、handshakeのレスポンスはHTTPとして扱わない。
  • 悪意あるスクリプトに攻撃の準備としてネットワーク環境の調査に使われるのを防ぐため、接続失敗の理由をスクリプトに通知してはいけない。

接続失敗の理由を開示してはいけない、というのは特定のシチュエーションの場合、と限定してあるのだが、この一文

A server that did not complete the opening handshake (e.g. because it was not a WebSocket server).

http://dev.w3.org/html5/websockets/

が示す範囲がよくわからなくて、これがもし403とか返した場合も該当するなら、サーバーはHTTPエラーコードを返さなくてはいけないがブラウザはエラーコードを握りつぶさなくてはいけない、ということになる。実際API仕様にもHTTPステータスを受け取れるとは書いてないし、そういうもんだと言われればそれまでだが、実際のところ困る。
chromeのコードもはっきりそうなっているっぽくて( @gtk2k さんに教えていただきました。ありがとうございます。 )、101以外のHTTPステータスを受け取っても、特に個別の動作はしないし、エラーイベントのどこを探してもHTTP statusが見つからない。リダイレクトもしない。Unexpected response code: 403という文字列がエラーコンソールに出てくるけど、javascriptからは拾えなそう。

一体どうすれば良いのかわからず、原本 http://tools.ietf.org/html/rfc6455 および http://www.w3.org/TR/websockets/ と、そこから辿れる旧バージョンを参照した。以下の様な時系列で変更が行われた模様。

WebSocket Protocol hybi-05以前

プロトコル仕様に以下の記述がある。

  • 記述1 ユーザーエージェントは、一部のシチュエーションではエラーの理由をスクリプトに伝えてはいけない
  • 記述2 handshakeのレスポンスとして101以外を受け取った時、WebSocket接続を終了する
User agents must not convey any failure information to scripts in a
   way that would allow a script to distinguish the following
   situations:

http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-05#section-7.1.1

If the status code received from the server is not 101, the client
      MUST fail the WebSocket connection.

http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-05#section-5.1

WebSocket Protocol hybi-06 February 25, 2011

プロトコル仕様から記述1が消える

ietf のメーリングリスト 28 Mar 2011

プロトコルレベルではHTTPをサポートするべきという方向に話が進む

(超意訳)これ(オープンリダイレクタの問題)って俺達の問題じゃなくてWHATWGの問題だよね。リダイレクトや認証機能が無しなんてやってらんないから入れちゃおうぜ。もしブラウザが当面リダイレクトをサポートしないって事になってもOK。後でブラウザ側の挙動だけ変えれば済むなら楽だし。

I don't think it is "our problem".     I think it is a "W3C/WHATWG
HTML-5 WG problem".

This is essentially an API issue for the browser websocket object.
There are plenty of ways around this (adding optional follow
redirects, exposing redirect responses etc. etc.),   but I think it is
the W3C and WHATWG HTML-5 working groups that should be where such
matters are decided.

As a web developer I really rather not have to go without redirection
and other common auth methods - and I take responsibility for my
servers not being open redirectors..... but would understand if the
W3C/WHATWG decided not to support these features - at least initially.

However, we should make the protocol such that if/when the API does
support these features, then it will only be an in browser change and
not an update of the protocol.

http://www.ietf.org/mail-archive/web/hybi/current/msg06954.html

WHATWG のメーリングリスト Mar 29 2011

APIレイヤでHTTPを理解するかは先送りの方向に

(超意訳)(HyBiのスレッドではHTTP使う方向になってるぜ、という話を受けて)オープンリダイレクタの問題はセキュリティ上の大穴だという意見に同意だ。httpが犯した過ちとそこで起こった問題を繰り返すべきではない。この問題は先送りにして、当面APIレベルではリダイレクトには対応しない、という方向で。

I absolutely agree that redirects are a big source of security
problems. If we are going to support them for websocket then I think
we need to learn from the mistakes of http as to not repeat the
problems that occurred there.

(中略)

But I'm totally fine with punting on this for the future and just
disallowing redirects on an API level for now.

http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2011-March/031079.html

WebSocket Protocol hybi-07 April 22, 2011

記述2部分が変更 101以外はHTTPとして処理する

If the status code received from the server is not 101, the client
      handles the response per HTTP procedures.  Otherwise, proceed as
      follows.

http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07#section-5.1

WebSocket API r1.210 May 24, 2011

旧記述2相当の内容がAPI仕様に引越し

Block redirects in WebSockets (whatwg r6148)
+    When the user agent validates the server's response during
+    the "establish a WebSocket connection" algorithm, if
+    the status code received from the server is not 101 (e.g. it is a
+    redirect), the user agent must fail the websocket
+    connection.

http://dev.w3.org/cvsweb/html5/websockets/Overview.html#rev1.210

WebSocket Protocol hybi-08 June 7, 2011

handshake失敗時にサーバーはエラー理由をHTTP error codeで表現しなさい、という記述が登場

 If the server does not wish to
          accept this connection, it MUST return an appropriate HTTP
          error code (e.g. 403 Forbidden) and abort the WebSocket
          handshake described in this section.

http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08#section-5.2.2

WebSocket Protocol RFC6455 PROPOSED STANDARD December 2011

現在のhandshakeのレスポンスの扱い protocolバージョン

Any status code other than 101 indicates that the WebSocket handshake
   has not completed and that the semantics of HTTP still apply.  The
   headers follow the status code.

http://tools.ietf.org/html/rfc6455#section-1.3

WebSocket API r1.271 Jul 11, 2012

記述1相当の内容が一年五ヶ月ぶりにAPI仕様として復活。

Clarify what codes are exposed in case of error, since this text was mysteriously removed from the RFC at some point. (whatwg r7175)
+   User agents must not convey any failure information to scripts
+   in a way that would allow a script to distinguish the following
+   situations:

http://dev.w3.org/cvsweb/html5/websockets/Overview.html#rev1.271

WebSocket API W3C Candidate Recommendation 20 September 2012

現在のhandshakeのレスポンスの扱い APIバージョン。 赤くて目立つようにHTTPは混ぜるな危険とwarning。

When the user agent validates the server's response during the "establish a WebSocket connection" algorithm, if the status code received from the server is not 101 (e.g. it is a redirect), the user agent must fail the WebSocket connection.

Following HTTP procedures here could introduce serious security problems in a Web browser context. For example, consider a host with a WebSocket server at one path and an open HTTP redirector at another. Suddenly, any script that can be given a particular WebSocket URL can be tricked into communicating to (and potentially sharing secrets with) any host on the Internet, even if the script checks that the URL has the right hostname.

http://www.w3.org/TR/websockets/#the-websocket-interface

というわけで、プロトコル仕様ではhandshakeのレスポンスはHTTPとして扱う方向になっているが、API仕様ではこれを無視する。将来的に統一するならAPI仕様が変更される。というのが現状のようです。接続失敗の理由をスクリプトから隠す件については結局良くわかりませんでした。MLはgoogle先生が最初に教えてくれた周辺しか読んでないので、他にも色々議論があったのかもしれません。

実際どう対処すればよいか。少なくとも現時点のブラウザの実装がエラーコードを隠蔽してしまう以上、サーバー側の実装をするときにhandshakeの結果をHTTPで返しても無駄な気はする。handshakeしてから即closeするか、またはエラー診断用のHTTPのエンドポイントを用意するとか。うーん。