Skip to content

Commit

Permalink
Make password hashing synchronous when using the /_config/admins API.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
janl committed Apr 23, 2012
1 parent 1ffff82 commit 08071a8
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 14 deletions.
7 changes: 6 additions & 1 deletion src/couchdb/couch_httpd_misc_handlers.erl
Expand Up @@ -212,7 +212,12 @@ handle_config_req(Req) ->
% PUT /_config/Section/Key % PUT /_config/Section/Key
% "value" % "value"
handle_approved_config_req(#httpd{method='PUT', path_parts=[_, Section, Key]}=Req, Persist) -> 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, ""), OldValue = couch_config:get(Section, Key, ""),
case couch_config:set(Section, Key, ?b2l(Value), Persist) of case couch_config:set(Section, Key, ?b2l(Value), Persist) of
ok -> ok ->
Expand Down
25 changes: 25 additions & 0 deletions src/couchdb/couch_passwords.erl
Expand Up @@ -13,6 +13,8 @@
-module(couch_passwords). -module(couch_passwords).


-export([simple/2, pbkdf2/3, pbkdf2/4, verify/2]). -export([simple/2, pbkdf2/3, pbkdf2/4, verify/2]).
-export([hash_admin_password/1, get_unhashed_admins/0]).

-include("couch_db.hrl"). -include("couch_db.hrl").


-define(MAX_DERIVED_KEY_LENGTH, (1 bsl 32 - 1)). -define(MAX_DERIVED_KEY_LENGTH, (1 bsl 32 - 1)).
Expand All @@ -23,6 +25,29 @@
simple(Password, Salt) -> simple(Password, Salt) ->
?l2b(couch_util:to_hex(crypto:sha(<<Password/binary, Salt/binary>>))). ?l2b(couch_util:to_hex(crypto:sha(<<Password/binary, Salt/binary>>))).


%% 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. %% Current scheme, much stronger.
-spec pbkdf2(binary(), binary(), integer()) -> string(). -spec pbkdf2(binary(), binary(), integer()) -> string().
pbkdf2(Password, Salt, Iterations) -> pbkdf2(Password, Salt, Iterations) ->
Expand Down
17 changes: 4 additions & 13 deletions src/couchdb/couch_server.erl
Expand Up @@ -129,20 +129,11 @@ hash_admin_passwords() ->
hash_admin_passwords(true). hash_admin_passwords(true).


hash_admin_passwords(Persist) -> hash_admin_passwords(Persist) ->
Iterations = couch_config:get("couch_httpd_auth", "iterations", "10000"),
lists:foreach( lists:foreach(
fun({_User, "-hashed-" ++ _}) -> fun({User, ClearPassword}) ->
ok; % already hashed HashedPassword = couch_passwords:hash_admin_password(ClearPassword),
({_User, "-pbkdf2-" ++ _}) -> couch_config:set("admins", User, ?b2l(HashedPassword), Persist)
ok; % already hashed end, couch_passwords:get_unhashed_admins()).
({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")).


init([]) -> init([]) ->
% read config and register for configuration changes % read config and register for configuration changes
Expand Down

0 comments on commit 08071a8

Please sign in to comment.