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 139cf454cf0791a52cd2113748ab5b61c317108f
Author: Robert Newson <[email protected]>
AuthorDate: Thu Oct 19 15:18:48 2023 +0100

    in-memory password hash cache
---
 rel/overlay/etc/default.ini             |  9 +++++
 src/couch/src/couch_passwords_cache.erl | 62 +++++++++++++++++++++++++++++++++
 src/couch/src/couch_primary_sup.erl     | 14 +++++++-
 3 files changed, 84 insertions(+), 1 deletion(-)

diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 1604f1585..9a9cc20f6 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -913,3 +913,12 @@ 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]
+;enable = true
\ No newline at end of file
diff --git a/src/couch/src/couch_passwords_cache.erl 
b/src/couch/src/couch_passwords_cache.erl
new file mode 100644
index 000000000..4ee91439c
--- /dev/null
+++ b/src/couch/src/couch_passwords_cache.erl
@@ -0,0 +1,62 @@
+% 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 config:get_boolean("couch_passwords_cache", "enable", true) of
+        true ->
+            authenticate_int(AuthModule, UserName, Password);
+        false ->
+            not_found
+    end.
+
+authenticate_int(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) ->
+    fast_pbkdf2:pbkdf2(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..cfb460bf8 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", 360_000)}
+                    ]
+                ]},
+                permanent, 5000, worker, [ets_lru]}
         ] ++ couch_servers(),
     {ok, {{one_for_one, 10, 3600}, Children}}.
 

Reply via email to