Return-Path: X-Original-To: apmail-couchdb-commits-archive@www.apache.org Delivered-To: apmail-couchdb-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id EB892106AE for ; Mon, 3 Mar 2014 16:59:05 +0000 (UTC) Received: (qmail 40418 invoked by uid 500); 3 Mar 2014 16:58:18 -0000 Delivered-To: apmail-couchdb-commits-archive@couchdb.apache.org Received: (qmail 40283 invoked by uid 500); 3 Mar 2014 16:58:16 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 40197 invoked by uid 99); 3 Mar 2014 16:58:13 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 03 Mar 2014 16:58:13 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 701F3933CBF; Mon, 3 Mar 2014 16:58:13 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: garren@apache.org To: commits@couchdb.apache.org Date: Mon, 03 Mar 2014 16:58:15 -0000 Message-Id: <17d7bb0375c744c08daecdf4fa273a68@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [03/34] couchdb commit: updated refs/heads/paginate-api-options to b63c791 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. Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/34888938 Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/34888938 Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/34888938 Branch: refs/heads/paginate-api-options Commit: 348889380c3b98d7f1a6c8963fc2eb4ee08c1db7 Parents: 45e17e5 Author: Klaus Trainer Authored: Wed Feb 19 23:17:02 2014 +0100 Committer: Klaus Trainer Committed: Sun Feb 23 18:59:22 2014 +0100 ---------------------------------------------------------------------- share/www/script/test/users_db_security.js | 61 ++++++++++++++++++++++++- src/couchdb/couch_httpd_auth.erl | 35 ++++++++++---- 2 files changed, 86 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb/blob/34888938/share/www/script/test/users_db_security.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/users_db_security.js b/share/www/script/test/users_db_security.js index 9bf9b8a..2a2bb9d 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 http://git-wip-us.apache.org/repos/asf/couchdb/blob/34888938/src/couchdb/couch_httpd_auth.erl ---------------------------------------------------------------------- diff --git a/src/couchdb/couch_httpd_auth.erl b/src/couchdb/couch_httpd_auth.erl index b8c4e26..08841fb 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), <>, 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} =