Posted by & filed under server.


nginxは標準ではDigest認証に対応していません.
そのため,Digest認証をするために,samizdatco氏の作ったnginx-http-auth-digestというサードパーティモジュールを利用します.

FreeBSDのportsで,HTTP_AUTH_DIGESTオプションをオンにすると導入されるモジュールも同じです.

このモジュールですが,とあるバグを抱えています.(2012/10/20現在)
URIによっては認証が正常に行われず,401←→認証の無限ループが発生するというものです.

例えば,以下のようなURLにアクセスした際に問題が発生します.

http://example.com/hoge.php?param=foo

このバグを修正するためのパッチを作成してみました.

このバグの原因は,サーバ側でクライアントから送られてきたハッシュをverifyする際に,ハッシュ計算に使用するパラメータの一部が仕様(RFC 2617)と異なっていることです.

仕様によると,認証に使用するRequest-Digestの計算には,digest-uri-value = request-uri = Request-URI(RFC 2616)が利用されています.
さらにRFC2616を参照すると,Request-URI = abs_pathとなります.

さて,このRequest-URIですが,パスだけではなく,クエリ文字列(つまり,例のURLの場合”?param=foo”の部分)も含まれています.
しかし,nginx-http-auth-digestモジュールのハッシュ計算部分では,クエリ文字列を除いたパスを使用していました.
このため,クライアント側から送られてきたハッシュと,サーバ側で計算したハッシュが一致せず,401を返し… というループに陥っていました.

このバグは,既にchazmcgarvey氏やjrkettle氏によって修正され,Pull Requestsも投げられていますが,まだmasterには組み込まれていないようです.

また,数人の方が幾つかの修正もされているみたいなので,それらをすべて統合した形のパッチを作成しました.

--- ../samizdatco-nginx-http-auth-digest-bd1c86a/ngx_http_auth_digest_module.c.orig     2011-12-29 09:00:32.000000000 +0900
+++ ../samizdatco-nginx-http-auth-digest-bd1c86a/ngx_http_auth_digest_module.c  2012-10-17 16:47:17.000000000 +0900
@@ -401,13 +401,13 @@
http_method.len = r->method_name.len+1;
http_method.data = ngx_pcalloc(r->pool, http_method.len);
if (http_method.data==NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
-  p = ngx_cpymem(http_method.data, r->method_name.data, r->method_end - r->method_name.data+1);
+  p = ngx_cpymem(http_method.data, r->method_name.data, r->method_name.len);

-  ha2_key.len = http_method.len + r->uri.len + 1;
+  ha2_key.len = http_method.len + r->unparsed_uri.len + 1;
ha2_key.data = ngx_pcalloc(r->pool, ha2_key.len);
if (ha2_key.data==NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
p = ngx_cpymem(ha2_key.data, http_method.data, http_method.len-1); *p++ = ':';
-  p = ngx_cpymem(p, r->uri.data, r->uri.len);
+  p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len);

HA2.len = 33;
HA2.data = ngx_pcalloc(r->pool, HA2.len);
@@ -417,7 +417,7 @@
ngx_hex_dump(HA2.data, hash, 16);

// calculate digest: md5(ha1:nonce:nc:cnonce:qop:ha2)
-  digest_key.len = HA1.len-1 + fields->nonce.len-1 + fields->nc.len-1 + fields->cnonce.len-1 + fields->qop.len-1 + HA2.len-1 + 5 + 1;
+  digest_key.len = HA1.len + fields->nonce.len + fields->nc.len + fields->cnonce.len + fields->qop.len + HA2.len;
digest_key.data = ngx_pcalloc(r->pool, digest_key.len);
if (digest_key.data==NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;

@@ -487,11 +487,11 @@
// recalculate the digest with a modified HA2 value (for rspauth) and emit the
// Authentication-Info header
ngx_memset(ha2_key.data, 0, ha2_key.len);
-    p = ngx_sprintf(ha2_key.data, ":%s", r->uri.data);
+    p = ngx_snprintf(ha2_key.data, 1 + r->unparsed_uri.len, ":%s", r->unparsed_uri.data);

ngx_memset(HA2.data, 0, HA2.len);
ngx_md5_init(&md5);
-    ngx_md5_update(&md5, ha2_key.data, r->uri.len);
+    ngx_md5_update(&md5, ha2_key.data, 1 + r->unparsed_uri.len);
ngx_md5_final(hash, &md5);
ngx_hex_dump(HA2.data, hash, 16);

@@ -771,8 +771,11 @@

void ngx_http_auth_digest_cleanup(ngx_event_t *ev){
if (ev->timer_set) ngx_del_timer(ev);
-  ngx_add_timer(ev, NGX_HTTP_AUTH_DIGEST_CLEANUP_INTERVAL);
-
+
+  if( !(ngx_quit || ngx_terminate || ngx_exiting ) ) {
+    ngx_add_timer(ev, NGX_HTTP_AUTH_DIGEST_CLEANUP_INTERVAL);
+  }
+
if (ngx_trylock(ngx_http_auth_digest_cleanup_lock)){
ngx_http_auth_digest_rbtree_prune(ev->log);
ngx_unlock(ngx_http_auth_digest_cleanup_lock);

これを,/usr/ports/www/nginx/files に patch-ngx_http_auth_digest_module.c などの適当な名前で作成します.

後は,いつも通りmakeをすればバグが修正されたモジュールがインストールされます.

  • tozawa

    nginxにnginx-http-auth-digestをaddonして起動してみましたが、想定ユーザーでログインを試みても401メッセージが返されてしまいます。このパッチを当てさせていただいたものも、オリジナルのものもどちらも同じ現象になります。設定は以下のとおりです。

    location /test/ {

    auth_digest ‘guest’;

    auth_digest_user_file /etc/nginx/passwd.digest;

    auth_digest_timeout 60s;

    auth_digest_expires 10s;

    auth_digest_replays 20;

    alias /var/www/test/;

    index index.html index.htm;

    }

    パスワードファイルはルート所有で744、nginxはnobodyユーザーで起動しています。

    access_logでリクエストヘッダを確認したところ、Authenticationヘッダは

    “Digest username=x22guestx22, realm=x22guestx22, nonce=x22679a716e5269d480x22, uri=x22/wsc/x22, algorithm=MD5, response=x22fb01be2a56f5c9833c75ad4fc3f9c200x22, qop=auth, nc=00000001, cnonce=x2236853f8413880610x22”

    このように受け取っており、リクエストは問題ないと思われます。

    情報が足りないかもしれませんが、ログインできない原因をご教授いただけたら、幸いです。

  • tozawa

    失礼いたしました。パスワードファイルのパーミッションは644でした。