diff options
author | Klaus Trainer <klaus_trainer@apache.org> | 2014-02-23 18:59:35 +0100 |
---|---|---|
committer | Klaus Trainer <klaus_trainer@apache.org> | 2014-02-23 18:59:35 +0100 |
commit | 8c867577cea2de7410eec33aa6358d1f20b5486d (patch) | |
tree | 6b87f14575e0f7275298c3bafc3b7b8913a3b826 | |
parent | 08cf09fc0ae828e05c3ab01b11d561b4e717e0c4 (diff) | |
parent | 348889380c3b98d7f1a6c8963fc2eb4ee08c1db7 (diff) | |
download | couchdb-8c867577cea2de7410eec33aa6358d1f20b5486d.tar.gz |
Merge branch '1780-upgrade-password-hashes-on-authentication'
-rw-r--r-- | share/www/script/couch_test_runner.js | 4 | ||||
-rw-r--r-- | share/www/script/test/auth_cache.js | 12 | ||||
-rw-r--r-- | share/www/script/test/cookie_auth.js | 11 | ||||
-rw-r--r-- | share/www/script/test/users_db_security.js | 61 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_auth.erl | 35 |
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} = |