Skip to content

Commit

Permalink
Merge pull request #4814 from apache/decouple_offline_hash_strength_f…
Browse files Browse the repository at this point in the history
…rom_online

Decouple offline hash strength from online
  • Loading branch information
rnewson committed Nov 11, 2023
2 parents 46a781f + dbcbc9a commit 5fd3579
Show file tree
Hide file tree
Showing 37 changed files with 1,854 additions and 229 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ DIALYZE_OPTS=$(shell echo "\
" | sed -e 's/[a-z]\{1,\}= / /g')
EXUNIT_OPTS=$(subst $(comma),$(space),$(tests))

TEST_OPTS="-c 'startup_jitter=0' -c 'default_security=admin_local'"
TEST_OPTS="-c 'startup_jitter=0' -c 'default_security=admin_local' -c 'iterations=9'"

################################################################################
# Main commands
Expand Down Expand Up @@ -343,7 +343,7 @@ mango-test: devclean all
.PHONY: weatherreport-test
# target: weatherreport-test - Run weatherreport against dev cluster
weatherreport-test: devclean escriptize
@dev/run -n 1 -a adm:pass --no-eval \
@dev/run "$(TEST_OPTS)" -n 1 -a adm:pass --no-eval \
'bin/weatherreport --etc dev/lib/node1/etc --level error'

################################################################################
Expand Down Expand Up @@ -565,7 +565,7 @@ nouveau-test-elixir: export MIX_ENV=integration
nouveau-test-elixir: elixir-init devclean
nouveau-test-elixir: couch nouveau
ifeq ($(with_nouveau), 1)
@dev/run -n 1 -q -a adm:pass --with-nouveau \
@dev/run "$(TEST_OPTS)" -n 1 -q -a adm:pass --with-nouveau \
--locald-config test/config/test-config.ini \
--no-eval 'mix test --trace --include test/elixir/test/config/nouveau.elixir'
endif
4 changes: 2 additions & 2 deletions Makefile.win
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ DIALYZE_OPTS=$(shell powershell -command "('apps=${apps} skip_deps=${skip_deps}

EXUNIT_OPTS=$(subst $(comma),$(space),$(tests))

TEST_OPTS="-c startup_jitter=0 -c default_security=admin_local"
TEST_OPTS="-c startup_jitter=0 -c default_security=admin_local -c iterations=9"

################################################################################
# Main commands
Expand Down Expand Up @@ -528,7 +528,7 @@ nouveau-test-elixir: export MIX_ENV=integration
nouveau-test-elixir: elixir-init devclean
nouveau-test-elixir: couch nouveau
ifeq ($(with_nouveau), 1)
@dev\run -n 1 -q -a adm:pass --with-nouveau \
@dev\run "$(TEST_OPTS)" -n 1 -q -a adm:pass --with-nouveau \
--locald-config test/elixir/test/config/test-config.ini \
--no-eval 'mix test --trace --include test/elixir/test/config/nouveau.elixir'
endif
7 changes: 4 additions & 3 deletions dev/run
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import base64
import contextlib
import functools
import glob
import hashlib
import inspect
import json
import ntpath
Expand Down Expand Up @@ -560,7 +561,7 @@ def gen_password():
return base64.b64encode(os.urandom(6)).decode()


def hashify(pwd, salt=COMMON_SALT, iterations=10, keylen=20):
def hashify(pwd, salt=COMMON_SALT, iterations=10, keylen=32):
"""
Implements password hashing according to:
- https://issues.apache.org/jira/browse/COUCHDB-1060
Expand All @@ -571,8 +572,8 @@ def hashify(pwd, salt=COMMON_SALT, iterations=10, keylen=20):
>>> hashify(candeira)
-pbkdf2-99eb34d97cdaa581e6ba7b5386e112c265c5c670,d1d2d4d8909c82c81b6c8184429a0739,10
"""
derived_key = pbkdf2_hex(pwd, salt, iterations, keylen)
return "-pbkdf2-%s,%s,%s" % (derived_key, salt, iterations)
derived_key = pbkdf2_hex(pwd, salt, iterations, keylen, hashfunc=hashlib.sha256)
return "-pbkdf2:sha256-%s,%s,%s" % (derived_key, salt, iterations)


def startup(ctx):
Expand Down
1 change: 1 addition & 0 deletions rebar.config.script
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ SubDirs = [
"src/chttpd",
"src/couch",
"src/couch_event",
"src/fast_pbkdf2",
"src/mem3",
"src/couch_index",
"src/couch_mrview",
Expand Down
15 changes: 14 additions & 1 deletion rel/overlay/etc/default.ini
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,12 @@ bind_address = 127.0.0.1
;timeout = 600 ; number of seconds before automatic logout
;auth_cache_size = 50 ; size is number of cache entries
;allow_persistent_cookies = true ; set to false to disallow persistent cookies
;iterations = 10 ; iterations for password hashing
;iterations = 50000 ; iterations for password hashing
;min_iterations = 1
;max_iterations = 1000000000
;password_scheme = pbkdf2
;pbkdf2_prf = sha256 ; must be one of sha | sha224 | sha256 | sha384 | sha512
;upgrade_hash_on_auth = true; whether to upgrade password hashes on successful authentication.

; List of Erlang RegExp or tuples of RegExp and an optional error message.
; Where a new password must match all RegExp.
Expand Down Expand Up @@ -912,3 +914,14 @@ enable = {{with_nouveau}}
;background_view_indexing_threshold = 80
;interactive_view_indexing_threshold = 90
;interactive_database_writes_threshold = 90

; To speed authentication on database requests when on-disk iteration count is
; high, an in-memory cache of password hashes with a lower iteration threshold
; is maintained.
; If you exclusively use authentication methods other than basic authentication
; (e.g, session cookies or proxy authentication) you might wish to disable this
; to avoid the slight per-request cost of this hashing.
[couch_passwords_cache]
;max_objects = 10000
;max_idle = 600000
;enable = true
6 changes: 4 additions & 2 deletions rel/reltool.config
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@

%% extra
nouveau,
recon
recon,
fast_pbkdf2
]},
{rel, "start_clean", "", [kernel, stdlib]},
{boot_rel, "couchdb"},
Expand Down Expand Up @@ -129,7 +130,8 @@

%% extra
{app, nouveau, [{incl_cond, include}]},
{app, recon, [{incl_cond, include}]}
{app, recon, [{incl_cond, include}]},
{app, fast_pbkdf2, [{incl_cond, include}]}
]}.

{overlay_vars, "couchdb.config"}.
Expand Down
2 changes: 1 addition & 1 deletion src/chttpd/src/chttpd_auth_cache.erl
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ get_user_creds(_Req, UserName) when is_binary(UserName) ->

update_user_creds(_Req, UserDoc, _Ctx) ->
{_, Ref} = spawn_monitor(fun() ->
case fabric:update_doc(dbname(), UserDoc, []) of
case fabric:update_doc(dbname(), UserDoc, [?ADMIN_CTX]) of
{ok, _} ->
exit(ok);
Else ->
Expand Down
4 changes: 2 additions & 2 deletions src/chttpd/test/eunit/chttpd_auth_hash_algorithms_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ test_hash_algorithm([DefaultHashAlgorithm | DecodingHashAlgorithmsList] = _, Sta
get_full_secret(?ADM_USER),
CurrentTime
),
{ok, ReqStatus, _, _} = test_request:request(get, base_url(), [{cookie, Cookie}]),
?assertEqual(Status, ReqStatus),
{ok, ReqStatus, Hdrs, Body} = test_request:request(get, base_url(), [{cookie, Cookie}]),
?assertMatch({Status, _, _}, {ReqStatus, Hdrs, Body}),
test_hash_algorithm(DecodingHashAlgorithmsList, Status).

test_hash_algorithms_should_work({_, {WorkingHashes, _, SupportedHashAlgorithms}} = _) ->
Expand Down
5 changes: 3 additions & 2 deletions src/chttpd/test/eunit/chttpd_auth_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,10 @@ test_hash_algorithm([DefaultHashAlgorithm | DecodingHashAlgorithmsList] = _) ->
{"X-Auth-CouchDB-Roles", "PROXY-USER-ROLE1, PROXY-USER-ROLE2"},
{"X-Auth-CouchDB-Token", Token}
],
{ok, _, _, ReqBody} = test_request:get(base_url() ++ "/_session", Headers),
{ok, RespStatus, _, RespBody} = test_request:get(base_url() ++ "/_session", Headers),
?assertMatch({200, _}, {RespStatus, RespBody}),
IsAuthenticatedViaProxy = couch_util:get_nested_json_value(
jiffy:decode(ReqBody), [<<"info">>, <<"authenticated">>]
jiffy:decode(RespBody), [<<"info">>, <<"authenticated">>]
),
?assertEqual(IsAuthenticatedViaProxy, <<"proxy">>),
test_hash_algorithm(DecodingHashAlgorithmsList).
Expand Down
1 change: 1 addition & 0 deletions src/couch/src/couch.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
% Upstream deps
ibrowse,
mochiweb,
fast_pbkdf2,

% ASF deps
couch_epi,
Expand Down
26 changes: 21 additions & 5 deletions src/couch/src/couch_auth_cache.erl
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ get_user_creds(_Req, UserName) ->
update_user_creds(_Req, UserDoc, _AuthCtx) ->
ok = ensure_users_db_exists(),
couch_util:with_db(users_db(), fun(UserDb) ->
{ok, _NewRev} = couch_db:update_doc(UserDb, UserDoc, []),
{ok, _NewRev} = couch_db:update_doc(UserDb, UserDoc, [?ADMIN_CTX]),
ok
end).

Expand All @@ -70,28 +70,44 @@ get_admin(UserName) when is_list(UserName) ->
% the name is an admin, now check to see if there is a user doc
% which has a matching name, salt, and password_sha
[HashedPwd, Salt] = string:tokens(HashedPwdAndSalt, ","),
make_admin_doc(HashedPwd, Salt);
make_admin_doc_simple(HashedPwd, Salt);
"-pbkdf2-" ++ HashedPwdSaltAndIterations ->
[HashedPwd, Salt, Iterations] = string:tokens(HashedPwdSaltAndIterations, ","),
make_admin_doc(HashedPwd, Salt, Iterations);
make_admin_doc_pbkdf2(<<"sha">>, HashedPwd, Salt, Iterations);
"-pbkdf2:sha-" ++ HashedPwdSaltAndIterations ->
[HashedPwd, Salt, Iterations] = string:tokens(HashedPwdSaltAndIterations, ","),
make_admin_doc_pbkdf2(<<"sha">>, HashedPwd, Salt, Iterations);
"-pbkdf2:sha224-" ++ HashedPwdSaltAndIterations ->
[HashedPwd, Salt, Iterations] = string:tokens(HashedPwdSaltAndIterations, ","),
make_admin_doc_pbkdf2(<<"sha224">>, HashedPwd, Salt, Iterations);
"-pbkdf2:sha256-" ++ HashedPwdSaltAndIterations ->
[HashedPwd, Salt, Iterations] = string:tokens(HashedPwdSaltAndIterations, ","),
make_admin_doc_pbkdf2(<<"sha256">>, HashedPwd, Salt, Iterations);
"-pbkdf2:sha384-" ++ HashedPwdSaltAndIterations ->
[HashedPwd, Salt, Iterations] = string:tokens(HashedPwdSaltAndIterations, ","),
make_admin_doc_pbkdf2(<<"sha384">>, HashedPwd, Salt, Iterations);
"-pbkdf2:sha512-" ++ HashedPwdSaltAndIterations ->
[HashedPwd, Salt, Iterations] = string:tokens(HashedPwdSaltAndIterations, ","),
make_admin_doc_pbkdf2(<<"sha512">>, HashedPwd, Salt, Iterations);
_Else ->
nil
end.

make_admin_doc(HashedPwd, Salt) ->
make_admin_doc_simple(HashedPwd, Salt) ->
[
{<<"roles">>, [<<"_admin">>]},
{<<"salt">>, ?l2b(Salt)},
{<<"password_scheme">>, <<"simple">>},
{<<"password_sha">>, ?l2b(HashedPwd)}
].

make_admin_doc(DerivedKey, Salt, Iterations) ->
make_admin_doc_pbkdf2(PRF, DerivedKey, Salt, Iterations) ->
[
{<<"roles">>, [<<"_admin">>]},
{<<"salt">>, ?l2b(Salt)},
{<<"iterations">>, list_to_integer(Iterations)},
{<<"password_scheme">>, <<"pbkdf2">>},
{<<"pbkdf2_prf">>, PRF},
{<<"derived_key">>, ?l2b(DerivedKey)}
].

Expand Down

0 comments on commit 5fd3579

Please sign in to comment.