summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Trainer <klaus_trainer@apache.org>2014-02-23 18:59:35 +0100
committerKlaus Trainer <klaus_trainer@apache.org>2014-02-23 18:59:35 +0100
commit8c867577cea2de7410eec33aa6358d1f20b5486d (patch)
tree6b87f14575e0f7275298c3bafc3b7b8913a3b826
parent08cf09fc0ae828e05c3ab01b11d561b4e717e0c4 (diff)
parent348889380c3b98d7f1a6c8963fc2eb4ee08c1db7 (diff)
downloadcouchdb-8c867577cea2de7410eec33aa6358d1f20b5486d.tar.gz
Merge branch '1780-upgrade-password-hashes-on-authentication'
-rw-r--r--share/www/script/couch_test_runner.js4
-rw-r--r--share/www/script/test/auth_cache.js12
-rw-r--r--share/www/script/test/cookie_auth.js11
-rw-r--r--share/www/script/test/users_db_security.js61
-rw-r--r--src/couchdb/couch_httpd_auth.erl35
5 files changed, 94 insertions, 29 deletions
diff --git a/share/www/script/couch_test_runner.js b/share/www/script/couch_test_runner.js
index c04e6b1da..7f435bfd3 100644
--- a/share/www/script/couch_test_runner.js
+++ b/share/www/script/couch_test_runner.js
@@ -460,9 +460,7 @@ CouchDB.user_prefix = "org.couchdb.user:";
CouchDB.prepareUserDoc = function(user_doc, new_password) {
user_doc._id = user_doc._id || CouchDB.user_prefix + user_doc.name;
if (new_password) {
- // handle the password crypto
- user_doc.salt = CouchDB.newUuids(1)[0];
- user_doc.password_sha = hex_sha1(new_password + user_doc.salt);
+ user_doc.password = new_password;
}
user_doc.type = "user";
if (!user_doc.roles) {
diff --git a/share/www/script/test/auth_cache.js b/share/www/script/test/auth_cache.js
index 57e6a8d98..2229c2070 100644
--- a/share/www/script/test/auth_cache.js
+++ b/share/www/script/test/auth_cache.js
@@ -184,11 +184,7 @@ couchTests.auth_cache = function(debug) {
hits_before = hits_after;
misses_before = misses_after;
- var new_salt = CouchDB.newUuids(1)[0];
- var new_passwd = hex_sha1("foobar" + new_salt);
- fdmanana.salt = new_salt;
- fdmanana.password_sha = new_passwd;
-
+ fdmanana.password = "foobar";
T(authDb.save(fdmanana).ok);
// cache was refreshed
@@ -206,11 +202,7 @@ couchTests.auth_cache = function(debug) {
misses_before = misses_after;
// and yet another update
- new_salt = CouchDB.newUuids(1)[0];
- new_passwd = hex_sha1("javascript" + new_salt);
- fdmanana.salt = new_salt;
- fdmanana.password_sha = new_passwd;
-
+ fdmanana.password = "javascript";
T(authDb.save(fdmanana).ok);
// cache was refreshed
diff --git a/share/www/script/test/cookie_auth.js b/share/www/script/test/cookie_auth.js
index 40b633b35..9b4bd6414 100644
--- a/share/www/script/test/cookie_auth.js
+++ b/share/www/script/test/cookie_auth.js
@@ -115,7 +115,7 @@ couchTests.cookie_auth = function(debug) {
// we can't create docs with malformed ids
var badIdDoc = CouchDB.prepareUserDoc({
- name: "foo"
+ name: "w00x"
}, "bar");
badIdDoc._id = "org.apache.couchdb:w00x";
@@ -153,8 +153,8 @@ couchTests.cookie_auth = function(debug) {
usersDb.deleteDoc(jchrisUserDoc);
T(false && "Can't delete other users docs. Should have thrown an error.");
} catch (e) {
- TEquals("forbidden", e.error);
- TEquals(403, usersDb.last_req.status);
+ TEquals("not_found", e.error);
+ TEquals(404, usersDb.last_req.status);
}
// TODO should login() throw an exception here?
@@ -197,8 +197,8 @@ couchTests.cookie_auth = function(debug) {
usersDb.save(jasonUserDoc);
T(false && "Can't update someone else's user doc. Should have thrown an error.");
} catch (e) {
- T(e.error == "forbidden");
- T(usersDb.last_req.status == 403);
+ T(e.error == "not_found");
+ T(usersDb.last_req.status == 404);
}
// test that you can't edit roles unless you are admin
@@ -272,7 +272,6 @@ couchTests.cookie_auth = function(debug) {
var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
usersDb.deleteDb();
- usersDb.createDb();
run_on_modified_server(
[
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} =