summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Trainer <klaus_trainer@posteo.de>2014-02-19 23:17:02 +0100
committerKlaus Trainer <klaus_trainer@apache.org>2014-02-23 18:59:22 +0100
commit348889380c3b98d7f1a6c8963fc2eb4ee08c1db7 (patch)
tree6b87f14575e0f7275298c3bafc3b7b8913a3b826
parent45e17e5fbb3c5364e7f8d0e6bb4d79e2e291ecfa (diff)
downloadcouchdb-348889380c3b98d7f1a6c8963fc2eb4ee08c1db7.tar.gz
Upgrade password hashes on authentication
We now upgrade user docs to the new PBKDF2 password scheme on successful authentication if the password hash is still from the old days where we only used plain SHA-1 for hashing salted passwords. Closes COUCHDB-1780.
-rw-r--r--share/www/script/test/users_db_security.js61
-rw-r--r--src/couchdb/couch_httpd_auth.erl35
2 files changed, 86 insertions, 10 deletions
diff --git a/share/www/script/test/users_db_security.js b/share/www/script/test/users_db_security.js
index 9bf9b8a1b..2a2bb9dfd 100644
--- a/share/www/script/test/users_db_security.js
+++ b/share/www/script/test/users_db_security.js
@@ -151,6 +151,63 @@ couchTests.users_db_security = function(debug) {
TEquals(true, userDoc.derived_key != jchrisDoc.derived_key,
"should have new derived_key");
+ // SHA-1 password hashes are upgraded to PBKDF2 on successful
+ // authentication
+ var rnewsonDoc = {
+ _id: "org.couchdb.user:rnewson",
+ type: "user",
+ name: "rnewson",
+ // password: "plaintext_password",
+ password_sha: "e29dc3aeed5abf43185c33e479f8998558c59474",
+ salt: "24f1e0a87c2e374212bda1073107e8ae",
+ roles: []
+ };
+
+ var password_sha = rnewsonDoc.password_sha,
+ salt = rnewsonDoc.salt,
+ derived_key,
+ iterations;
+
+ usersDb.save(rnewsonDoc);
+ rnewsonDoc = open_as(usersDb, rnewsonDoc._id, "jan");
+ T(!rnewsonDoc.password_scheme);
+ T(!rnewsonDoc.derived_key);
+ T(!rnewsonDoc.iterations);
+
+ // check that we don't upgrade when the password is wrong
+ TEquals("unauthorized", CouchDB.login("rnewson", "wrong_password").error);
+ rnewsonDoc = open_as(usersDb, rnewsonDoc._id, "jan");
+ TEquals(salt, rnewsonDoc.salt);
+ TEquals(password_sha, rnewsonDoc.password_sha);
+ T(!rnewsonDoc.password_scheme);
+ T(!rnewsonDoc.derived_key);
+ T(!rnewsonDoc.iterations);
+
+ TEquals(true, CouchDB.login("rnewson", "plaintext_password").ok);
+ rnewsonDoc = usersDb.open(rnewsonDoc._id);
+ TEquals("pbkdf2", rnewsonDoc.password_scheme);
+ T(rnewsonDoc.salt != salt);
+ T(!rnewsonDoc.password_sha);
+ T(rnewsonDoc.derived_key);
+ T(rnewsonDoc.iterations);
+
+ salt = rnewsonDoc.salt,
+ derived_key = rnewsonDoc.derived_key,
+ iterations = rnewsonDoc.iterations;
+
+ // check that authentication is still working
+ // and everything is staying the same now
+ CouchDB.logout();
+ TEquals(true, CouchDB.login("rnewson", "plaintext_password").ok);
+ rnewsonDoc = usersDb.open(rnewsonDoc._id);
+ TEquals("pbkdf2", rnewsonDoc.password_scheme);
+ TEquals(salt, rnewsonDoc.salt);
+ T(!rnewsonDoc.password_sha);
+ TEquals(derived_key, rnewsonDoc.derived_key);
+ TEquals(iterations, rnewsonDoc.iterations);
+
+ CouchDB.logout();
+
// user should not be able to read another user's user document
var fdmananaDoc = {
_id: "org.couchdb.user:fdmanana",
@@ -209,11 +266,11 @@ couchTests.users_db_security = function(debug) {
// admin should be able to read from any view
var result = view_as(usersDb, "user_db_auth/test", "jan");
- TEquals(3, result.total_rows, "should allow access and list two users to admin");
+ TEquals(4, result.total_rows, "should allow access and list four users to admin");
// db admin should be able to read from any view
var result = view_as(usersDb, "user_db_auth/test", "benoitc");
- TEquals(3, result.total_rows, "should allow access and list two users to db admin");
+ TEquals(4, result.total_rows, "should allow access and list four users to db admin");
// non-admins can't read design docs
diff --git a/src/couchdb/couch_httpd_auth.erl b/src/couchdb/couch_httpd_auth.erl
index b8c4e2602..08841fb67 100644
--- a/src/couchdb/couch_httpd_auth.erl
+++ b/src/couchdb/couch_httpd_auth.erl
@@ -68,11 +68,14 @@ default_authentication_handler(Req) ->
nil ->
throw({unauthorized, <<"Name or password is incorrect.">>});
UserProps ->
- case authenticate(?l2b(Pass), UserProps) of
+ UserName = ?l2b(User),
+ Password = ?l2b(Pass),
+ case authenticate(Password, UserProps) of
true ->
+ UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps),
Req#httpd{user_ctx=#user_ctx{
- name=?l2b(User),
- roles=couch_util:get_value(<<"roles">>, UserProps, [])
+ name=UserName,
+ roles=couch_util:get_value(<<"roles">>, UserProps2, [])
}};
_Else ->
throw({unauthorized, <<"Name or password is incorrect.">>})
@@ -263,15 +266,16 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) ->
UserName = ?l2b(couch_util:get_value("name", Form, "")),
Password = ?l2b(couch_util:get_value("password", Form, "")),
?LOG_DEBUG("Attempt Login: ~s",[UserName]),
- User = case couch_auth_cache:get_user_creds(UserName) of
+ UserProps = case couch_auth_cache:get_user_creds(UserName) of
nil -> [];
Result -> Result
end,
- UserSalt = couch_util:get_value(<<"salt">>, User, <<>>),
- case authenticate(Password, User) of
+ case authenticate(Password, UserProps) of
true ->
+ UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps),
% setup the session cookie
Secret = ?l2b(ensure_cookie_auth_secret()),
+ UserSalt = couch_util:get_value(<<"salt">>, UserProps2),
CurrentTime = make_cookie_time(),
Cookie = cookie_auth_cookie(Req, ?b2l(UserName), <<Secret/binary, UserSalt/binary>>, CurrentTime),
% TODO document the "next" feature in Futon
@@ -284,8 +288,8 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) ->
send_json(Req#httpd{req_body=ReqBody}, Code, Headers,
{[
{ok, true},
- {name, couch_util:get_value(<<"name">>, User, null)},
- {roles, couch_util:get_value(<<"roles">>, User, [])}
+ {name, couch_util:get_value(<<"name">>, UserProps2, null)},
+ {roles, couch_util:get_value(<<"roles">>, UserProps2, [])}
]});
_Else ->
% clear the session
@@ -340,6 +344,21 @@ maybe_value(_Key, undefined, _Fun) -> [];
maybe_value(Key, Else, Fun) ->
[{Key, Fun(Else)}].
+maybe_upgrade_password_hash(UserName, Password, UserProps) ->
+ case couch_util:get_value(<<"password_scheme">>, UserProps, <<"simple">>) of
+ <<"simple">> ->
+ DbName = ?l2b(couch_config:get("couch_httpd_auth", "authentication_db", "_users")),
+ couch_util:with_db(DbName, fun(UserDb) ->
+ UserProps2 = proplists:delete(<<"password_sha">>, UserProps),
+ UserProps3 = [{<<"password">>, Password} | UserProps2],
+ NewUserDoc = couch_doc:from_json_obj({UserProps3}),
+ {ok, _NewRev} = couch_db:update_doc(UserDb, NewUserDoc, []),
+ couch_auth_cache:get_user_creds(UserName)
+ end);
+ _ ->
+ UserProps
+ end.
+
authenticate(Pass, UserProps) ->
UserSalt = couch_util:get_value(<<"salt">>, UserProps, <<>>),
{PasswordHash, ExpectedHash} =