Digest認証でBad Request(Apache,IE)

5年前はまだNetscape6とIE5ぐらいしか対応してなかった(と思う)Digest認証、いくらなんでももう使えないクライアントもそうそういないよね、ってなわけでDigest認証を使うことにしたんです。
Digest認証というのは、Basic認証の仲間で、Basic認証がパスワードを平文で送るのに対して、パスワード文字列を暗号化して送るからパケット盗み見されても大丈夫、というわけ。
Digest認証を使えばセキュリティ上のリスク対策は万全!なんてものでは決してないけれど、Basic認証を使うぐらいならDigest認証を使ったほうが良い、そんなものです。大事なもんならSSL使え、って感じだしね。
さて、そんなDigest認証、最近のブラウザなら当たり前に使えるもんだと思っていたら、IE6ったらマトモに使えねーでやんの。IE6が相手になるであろうDigest認証を使うApacheには1行追加で設定が必要。以下、その詳細。

GET /test/sample.cgi?data=1 HTTP/1.1
Accept: image/gif, image/x-xbitmap, …(長いので以下略)
Accept-Language: en
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)
Host:example.jp
Connection: Keep-Alive

Webサーバに繋いで普通にリクエストを送ると、「認証が必要ですよ」と、サーバは以下返答をする。

HTTP/1.1 401 Authorization Required
Date: Thu, 11 May 2006 05:15:52 GMT
Server: Apache
WWW-Authenticate: Digest realm="digest auth test", nonce="abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOP", algorithm=MD5, domain="/test/",qop="auth"
Content-Length: 401
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
(以下、401 Authrization Requiredのドキュメント。省略)

サーバからは、「認証が必要ですよ」だけでなく、Digest認証を行うために必要なレルム情報や、ノンス情報、暗号化アルゴリズムの指定等が返ってくる。これらの情報を使って、認証情報を生成し、次のリクエストでそれを送信することで、認証を行うわけだ。サーバはノンス情報を定期的に変更して送ってくるし、クライアントが送信する認証情報はパスワードにノンスを混ぜてMD5ハッシュを計算させるので、認証情報はそれなりの頻度で異なるために安全な通信が実現できる。
さて、上記情報をもらったIEは認証情報を付加して以下のようなリクエストを送信する。

GET /test/sample.cgi?data=1 HTTP/1.1
Accept: image/gif, …(長いので以下略)
Accept-Language: en
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE6.0; Windows NT5.1; SV1; .NET CLR 1.1.4322)
Host: example.jp
Connection: Keep-Alive
Authorization: Digest username="inuz", realm="digest auth test", qop="auth", algorithm="MD5", uri="/test/sample.cgi", nonce="abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOP", nc=00000001, cnonce="1234567890abcdefghijklmnopqrstuv", response="aioeokakikukekosasisusesotatitut"

上記HTTPリクエストヘッダのうち、Authrization: ヘッダにDigest認証の認証情報が入っている。username="inuz"は普通にユーザ情報、responseにクライアント側で暗号化したパスワード情報が入っている。サーバ側では、クライアントから送られてきたこれらの情報を元に、認証の可否を決定する。
さて、Digest認証の手順としてはそれほど間違いには見えないが、実はここに書いたリクエストはDigest認証として不備があり、普通のApacheは "Bad Request" を返してしまう。
その原因は uri="/test/sample.cgi" で、本当は uri="/test/sample.cgi?data=1" が正しい。Apacheは、認証対象のuriとして送ってきた認証情報とGETしようとしているURIが異なるため、これを Bad Request と判定する。
Google神様に聞いてみると、IEは ?以下の引数部分を落として Authorizationヘッダの uri 情報を作る愚かなマネをするんだと。そしてMicrosoftはこれを直さないんだと。いまだブラウザシェア圧倒的1位のIEが直さないなんてね。
ここまでの情報だと、IEを相手にDigest認証を使うときは method="GET" なCGIは作れないなぁ、なんて思っちゃうんだけれど、Apacheのほうが対応してくれてた。Apache2.0.51以降だよ。

# MSIE: GET requests with a query string are not RFC compliant
BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On

MSIEだったら、uri="hoge" は比較しないでください、というもの。Digest認証が使いたいなら、実質この設定は必須というところかね。CGIないなら良いけどさ。
http://httpd.apache.org/docs/2.0/mod/mod_auth_digest.html#msie