From 08071a80e0dc78b744cf56179c0aaac0a4390fde Mon Sep 17 00:00:00 2001 From: Jan Lehnardt Date: Mon, 23 Apr 2012 20:04:49 +0200 Subject: [PATCH] Make password hashing synchronous when using the /_config/admins API. This should account for many intermittent JavaScript test suite errors. The patch retains hashing admins on couch_server start to account for users editing their .ini files directly. Knowledge about password hash prefixes and the password hashing itself has been moved to couch_passwords. Thanks to Dale Harvey and Robert Newson for helping me to hunt this down and shaping and reviewing the patch. --- src/couchdb/couch_httpd_misc_handlers.erl | 7 ++++++- src/couchdb/couch_passwords.erl | 25 +++++++++++++++++++++++ src/couchdb/couch_server.erl | 17 ++++----------- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/couchdb/couch_httpd_misc_handlers.erl b/src/couchdb/couch_httpd_misc_handlers.erl index 38dd98ec..6ee49e0b 100644 --- a/src/couchdb/couch_httpd_misc_handlers.erl +++ b/src/couchdb/couch_httpd_misc_handlers.erl @@ -212,7 +212,12 @@ handle_config_req(Req) -> % PUT /_config/Section/Key % "value" handle_approved_config_req(#httpd{method='PUT', path_parts=[_, Section, Key]}=Req, Persist) -> - Value = couch_httpd:json_body(Req), + Value = case Section of + <<"admins">> -> + couch_passwords:hash_admin_password(couch_httpd:json_body(Req)); + _ -> + couch_httpd:json_body(Req) + end, OldValue = couch_config:get(Section, Key, ""), case couch_config:set(Section, Key, ?b2l(Value), Persist) of ok -> diff --git a/src/couchdb/couch_passwords.erl b/src/couchdb/couch_passwords.erl index e5de8788..ce87f2c5 100644 --- a/src/couchdb/couch_passwords.erl +++ b/src/couchdb/couch_passwords.erl @@ -13,6 +13,8 @@ -module(couch_passwords). -export([simple/2, pbkdf2/3, pbkdf2/4, verify/2]). +-export([hash_admin_password/1, get_unhashed_admins/0]). + -include("couch_db.hrl"). -define(MAX_DERIVED_KEY_LENGTH, (1 bsl 32 - 1)). @@ -23,6 +25,29 @@ simple(Password, Salt) -> ?l2b(couch_util:to_hex(crypto:sha(<>))). +%% CouchDB utility functions +-spec hash_admin_password(binary()) -> binary(). +hash_admin_password(ClearPassword) -> + Iterations = couch_config:get("couch_httpd_auth", "iterations", "10000"), + Salt = couch_uuids:random(), + DerivedKey = couch_passwords:pbkdf2(?b2l(ClearPassword), Salt, + list_to_integer(Iterations)), + ?l2b("-pbkdf2-" ++ ?b2l(DerivedKey) ++ "," + ++ ?b2l(Salt) ++ "," + ++ Iterations). + +-spec get_unhashed_admins() -> list(). +get_unhashed_admins() -> + lists:filter( + fun({_User, "-hashed-" ++ _}) -> + false; % already hashed + ({_User, "-pbkdf2-" ++ _}) -> + false; % already hashed + ({_User, _ClearPassword}) -> + true + end, + couch_config:get("admins")). + %% Current scheme, much stronger. -spec pbkdf2(binary(), binary(), integer()) -> string(). pbkdf2(Password, Salt, Iterations) -> diff --git a/src/couchdb/couch_server.erl b/src/couchdb/couch_server.erl index cf66b86f..8ebb7d62 100644 --- a/src/couchdb/couch_server.erl +++ b/src/couchdb/couch_server.erl @@ -129,20 +129,11 @@ hash_admin_passwords() -> hash_admin_passwords(true). hash_admin_passwords(Persist) -> - Iterations = couch_config:get("couch_httpd_auth", "iterations", "10000"), lists:foreach( - fun({_User, "-hashed-" ++ _}) -> - ok; % already hashed - ({_User, "-pbkdf2-" ++ _}) -> - ok; % already hashed - ({User, ClearPassword}) -> - Salt = couch_uuids:random(), - DerivedKey = couch_passwords:pbkdf2(ClearPassword, Salt, - list_to_integer(Iterations)), - couch_config:set("admins", - User, "-pbkdf2-" ++ ?b2l(DerivedKey) ++ "," ++ ?b2l(Salt) ++ - "," ++ Iterations, Persist) - end, couch_config:get("admins")). + fun({User, ClearPassword}) -> + HashedPassword = couch_passwords:hash_admin_password(ClearPassword), + couch_config:set("admins", User, ?b2l(HashedPassword), Persist) + end, couch_passwords:get_unhashed_admins()). init([]) -> % read config and register for configuration changes