Cookbook > Apache_api

Apache 2.0.xにおけるSHA-1パスワードの問題

By Hiroyuki OYAMA Mon Aug 11 19:04:00 2003

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
と3種類のエンコード形式を認識していた。しかしApache 2.0.x系に実装されているパスワード検証用の関数apr_password_validate()には
  • crypt()
  • MD5
の2種類のみを認識する実装が行われている。

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                      */
};
ダウンロードする場合は こちらをどうぞ。
やっていることは簡単で
  1. ap_get_basic_auth_pw()で受信したパスワードを抽出
  2. find_passwd()でパスワードファイル内を検索
  3. 対象のユーザのエントリがあった場合、apr_sha1_base64()で受信したパスワードをSHA-1 + base64でエンコード
  4. エンコード結果をパスワードファイルのエントリと比較し認証
です。

この実装の問題

如何に手を抜くかがテーマの実装なので、SHA-1以外の形式のエントリを対象にする場合、パスワードファイルを2回以上検索してしまう問題があります。この問題の対処は簡単で、SHA-1以外の場合にreturn DECLINEDせずに、apr_password_validate()で引き続き検証するだけです。
それをやらないのは同じ機能を2箇所で実装するのが嫌、という単に筆者の趣味です。
もうすこし真面目にmod_auth_compat13.cとか作ってみても良いかもしれませんね。

Comments

Post a comment

Name:


URL:


Comments:


WebエンジニアのためのApacheモジュールプログラミングガイド

ApacheをHackする!
モジュールプログラミング強烈初体験!!
定価: 2,919円(税込)
ISBN: 4-7741-1799-4

hiroyuki_oyama IM status

Apache Users

Apache Modules

CPAN


Home > Cookbook > Apache_api > Apache 2.0.xにおけるSHA-1パスワードの問題