This is an automated email from the ASF dual-hosted git repository. rnewson pushed a commit to branch decouple_offline_hash_strength_from_online in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 360ae5979812cf8e8683ce4dcc5b95b1984805c2 Author: Robert Newson <[email protected]> AuthorDate: Thu Oct 19 15:18:48 2023 +0100 in-memory password hash cache --- src/couch/src/couch_passwords_cache.erl | 54 +++++++++++++++++++++++++++++++++ src/couch/src/couch_primary_sup.erl | 14 ++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/couch/src/couch_passwords_cache.erl b/src/couch/src/couch_passwords_cache.erl new file mode 100644 index 000000000..d0b277a51 --- /dev/null +++ b/src/couch/src/couch_passwords_cache.erl @@ -0,0 +1,54 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. +% +% password hashes on disk can take a long time to verify. This is by design, to +% guard against offline attacks. This module adds an in-memory cache for password +% verification to speed up verification after a successful slow verification from +% the hash stored on disk, in order not to transfer the deliberate offline attack +% protection to database requests. +% +% In memory we record a PBKDF_SHA256 derivation using a low number of iterations +% and check against this if present. Entries in couch_passwords_cache expire automatically +% and the maximum number of cached entries is configurable. + +-module(couch_passwords_cache). + +-define(CACHE, couch_passwords_cache_lru). + +-export([start_link/0]). + +% public api +-export([authenticate/3, insert/3]). + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +% public functions +-spec authenticate(AuthModule :: atom(), UserName :: binary(), Password :: binary()) -> + not_found | boolean(). +authenticate(AuthModule, UserName, Password) -> + case ets_lru:lookup_d(?CACHE, {AuthModule, UserName}) of + not_found -> + not_found; + {ok, {Salt, Expected}} -> + Actual = hash(Password, Salt), + couch_passwords:verify(Expected, Actual) + end. + +-spec insert(AuthModule :: atom(), UserName :: binary(), Password :: binary()) -> ok. +insert(AuthModule, UserName, Password) -> + Salt = couch_uuids:random(), + DerivedKey = hash(Password, Salt), + ets_lru:insert(?CACHE, {AuthModule, UserName}, {Salt, DerivedKey}). + +hash(Password, Salt) -> + crypto:pbkdf2_hmac(sha256, Password, Salt, _Iterations = 5, _KeyLen = 32). diff --git a/src/couch/src/couch_primary_sup.erl b/src/couch/src/couch_primary_sup.erl index 1eae87160..5c27a2850 100644 --- a/src/couch/src/couch_primary_sup.erl +++ b/src/couch/src/couch_primary_sup.erl @@ -23,7 +23,19 @@ init([]) -> {couch_task_status, {couch_task_status, start_link, []}, permanent, brutal_kill, worker, [couch_task_status]}, {couch_password_hasher, {couch_password_hasher, start_link, []}, permanent, brutal_kill, - worker, [couch_password_hasher]} + worker, [couch_password_hasher]}, + {couch_passwords_cache_lru, + {ets_lru, start_link, [ + couch_passwords_cache_lru, + + [ + {max_objects, + config:get_integer("couch_passwords_cache", "max_objects", 10_000)}, + {max_lifetime, + config:get_integer("couch_passwords_cache", "max_lifetime", 60_000)} + ] + ]}, + permanent, 5000, worker, [ets_lru]} ] ++ couch_servers(), {ok, {{one_for_one, 10, 3600}, Children}}.
