Apache 2.0.x系ではSHA-1によるNetscape/LDAPスタイルのパスワードでユーザ認証をすることができないため、Apache 1.3.x系からの移行に支障をきたす場合があります。これをApache 2.0.x系で認証できるようにする方法。
背景
Apache 2.0.x系付属のhtpasswdプログラムは-sオプションでSHA-1を利用した下記のようなNetscape/LDAPスタイルのパスワードを生成することができる。username:{SHA}zM2crwUwUL/E3K20tMXohJ1Emj8=しかし、Apache 2.0.x系はこのパスワードファイルを使用したユーザ認証を行うことができない。これはApache 2.0.x系はSHA-1形式の文字列をパスワードとして認識することができない(しない)ためです。このSHA-1形式のパスワードはApache 1.3.x系では利用できていた為、移行を考えた場合に問題になる。
Apache 1.3.x系に実装されていたパスワード検証用の関数ap_validate_password()には
- crypt()
- MD5
- SHA-1
- crypt()
- MD5
2.0.x系でSHA-1のエンコードが除外された詳細な経緯や理由は調査していませんが、ソースパッケージに付属しているドキュメント($src_top/support/SHA1/README.sha1)によると、SHA-1形式のパスワードはNetscape社のWebサーバなどで利用されており、そこからApache HTTP Serverへの移行をスムーズに行うためにサポートされていたようです。しかしsaltなどを利用せずにパスワードの文字列を単純にSHA-1でエンコードしているため、セキュリティ的に脆弱なために現在は推奨されていないようです。
ネットワーク越しにBasic認証するんだったら、セキュリティもなんも無いじゃんとか突っ込みたくなるかもしれませんが、別のレイヤーの問題ということで我慢。
対応
とはいったものの、実際問題ないと困るのもまた人情。この問題は SHA-1形式のパスワードを認識するApache 2.0.x用のaaa(Authorization And Authentication)モジュールを実装し組み込めば対処できます。実装方法についてはいくつか考え方があると思いますが、SHA-1形式のパスワードの場合のみ判断を行い、それ以外の形式の場合はDECLINEDをreturnし、本来のmod_auth.cモジュールに処理を引き継ぐカッコウで実装してみます。
#include "apr_sha1.h" #include "ap_config.h" #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" extern module AP_MODULE_DECLARE_DATA auth_module; typedef struct { char *auth_pwfile; char *auth_grpfile; int auth_authoritative; } auth_config_rec; static char *find_passwd(request_rec *r, char *user, char *auth_pwfile) { ap_configfile_t *f; char l[MAX_STRING_LEN]; const char *rpw, *w; apr_status_t status; if ((status = ap_pcfg_openfile(&f, r->pool, auth_pwfile)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "Could not open password file: ", auth_pwfile); return NULL; } while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { if ((l[0] == '#') || (!l[0])) continue; rpw = l; w = ap_getword(r->pool, &rpw, ':'); if (strcmp(user, w) == 0) { ap_cfg_closefile(f); return ap_getword(r->pool, &rpw, ':'); } } ap_cfg_closefile(f); return NULL; } static int authenticate_sha1_ldapstyle_basic_user(request_rec *r) { auth_config_rec *conf = ap_get_module_config(r->per_dir_config, &auth_module); const char *sent_pw; char *hash, comp_hash[120]; int res; if ((res = ap_get_basic_auth_pw(r, &sent_pw))) return res; if (!conf->auth_pwfile) return DECLINED; if (!(hash = find_passwd(r, r->user, conf->auth_pwfile))) { if (!(conf->auth_authoritative)) return DECLINED; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "user not found(ldapstyle): ", r->user, r->uri); ap_note_basic_auth_failure(r); return HTTP_UNAUTHORIZED; } if (strncmp(hash, APR_SHA1PW_ID, APR_SHA1PW_IDLEN) != 0) { return DECLINED; } /* compute Netscape/LDAP Style SHA1 password string */ apr_sha1_base64(sent_pw, strlen(sent_pw), comp_hash); if (strcmp(comp_hash, hash) != 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "user : authentication failure for \"\"(ldapstyle): " "Password Mismatch", r->user, r->uri); ap_note_basic_auth_failure(r); return HTTP_UNAUTHORIZED; } return OK; } static void register_hooks(apr_pool_t *p) { static const char * const successor[] = { "mod_auth.c", NULL }; ap_hook_check_user_id(authenticate_sha1_ldapstyle_basic_user, NULL, successor, APR_HOOK_MIDDLE); } /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA auth_sha1_ldapstyle_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ NULL, /* table of config file commands */ register_hooks /* register hooks */ };ダウンロードする場合は こちらをどうぞ。
やっていることは簡単で
- ap_get_basic_auth_pw()で受信したパスワードを抽出
- find_passwd()でパスワードファイル内を検索
- 対象のユーザのエントリがあった場合、apr_sha1_base64()で受信したパスワードをSHA-1 + base64でエンコード
- エンコード結果をパスワードファイルのエントリと比較し認証
この実装の問題
如何に手を抜くかがテーマの実装なので、SHA-1以外の形式のエントリを対象にする場合、パスワードファイルを2回以上検索してしまう問題があります。この問題の対処は簡単で、SHA-1以外の場合にreturn DECLINEDせずに、apr_password_validate()で引き続き検証するだけです。それをやらないのは同じ機能を2箇所で実装するのが嫌、という単に筆者の趣味です。
もうすこし真面目にmod_auth_compat13.cとか作ってみても良いかもしれませんね。