You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2023/02/27 20:34:42 UTC

[couchdb] 02/02: This enables configuring FIPS mode at runtime without the need for a custom build.

This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 54879f9a5d093b8000d64070e7de323e155f2a2a
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Fri Feb 24 18:16:19 2023 -0500

    This enables configuring FIPS mode at runtime without the need for a custom build.
    
    Issue: #4442
---
 rel/overlay/etc/vm.args                  | 11 +++++++
 src/config/src/config.erl                | 12 ++++++++
 src/config/test/config_tests.erl         |  6 ++++
 src/couch/src/couch_hash.erl             | 30 +++++++++++++++---
 src/couch/src/couch_server.erl           |  8 -----
 src/couch/test/eunit/couch_hash_test.erl | 52 ++++++++++++++++++++++++++++++++
 6 files changed, 107 insertions(+), 12 deletions(-)

diff --git a/rel/overlay/etc/vm.args b/rel/overlay/etc/vm.args
index 2c011e405..174fba1c5 100644
--- a/rel/overlay/etc/vm.args
+++ b/rel/overlay/etc/vm.args
@@ -99,3 +99,14 @@
 #-proto_dist couch
 #-couch_dist no_tls '"clouseau@127.0.0.1"'
 #-ssl_dist_optfile <path/to/couch_ssl_dist.conf>
+
+# Enable FIPS mode
+#   https://www.erlang.org/doc/apps/crypto/fips.html
+#   Ensure that:
+#    - Erlang is built with --enable-fips configuration option
+#    - Crypto library (e.g. OpenSSL) supports this mode
+#
+# When the mode is successfully enabled "Welcome" message should show `fips`
+# in the features list.
+#
+#-crypto fips_mode true
diff --git a/src/config/src/config.erl b/src/config/src/config.erl
index 7cd7251e6..72dff72d8 100644
--- a/src/config/src/config.erl
+++ b/src/config/src/config.erl
@@ -229,6 +229,17 @@ is_enabled(Feature) when is_atom(Feature) ->
     Map = persistent_term:get({?MODULE, ?FEATURES}, #{}),
     maps:get(Feature, Map, false).
 
+% Some features like FIPS mode must be enabled earlier before couch, couch_epi
+% start up
+%
+enable_early_features() ->
+    % Mark FIPS if enabled
+    case crypto:info_fips() == enabled of
+        true ->
+            enable_feature(fips);
+        false ->
+            ok
+    end.
 
 listen_for_changes(CallbackModule, InitialState) ->
     config_listener_mon:subscribe(CallbackModule, InitialState).
@@ -237,6 +248,7 @@ subscribe_for_changes(Subscription) ->
     config_notifier:subscribe(Subscription).
 
 init(IniFiles) ->
+    enable_early_features(),
     ets:new(?MODULE, [named_table, set, protected, {read_concurrency, true}]),
     lists:map(
         fun(IniFile) ->
diff --git a/src/config/test/config_tests.erl b/src/config/test/config_tests.erl
index 3d3ee9c94..90d430a87 100644
--- a/src/config/test/config_tests.erl
+++ b/src/config/test/config_tests.erl
@@ -651,11 +651,14 @@ should_enable_features() ->
 
     ?assertEqual(ok, config:enable_feature(snek)),
     ?assertEqual([snek], config:features()),
+    ?assert(config:is_enabled(snek)),
 
     ?assertEqual(ok, config:enable_feature(snek)),
     ?assertEqual([snek], config:features()),
 
     ?assertEqual(ok, config:enable_feature(dogo)),
+    ?assert(config:is_enabled(dogo)),
+    ?assert(config:is_enabled(snek)),
     ?assertEqual([dogo, snek], config:features()).
 
 should_disable_features() ->
@@ -666,9 +669,11 @@ should_disable_features() ->
     ?assertEqual([snek], config:features()),
 
     ?assertEqual(ok, config:disable_feature(snek)),
+    ?assertNot(config:is_enabled(snek)),
     ?assertEqual([], config:features()),
 
     ?assertEqual(ok, config:disable_feature(snek)),
+    ?assertNot(config:is_enabled(snek)),
     ?assertEqual([], config:features()).
 
 should_keep_features_on_config_restart() ->
@@ -678,6 +683,7 @@ should_keep_features_on_config_restart() ->
     config:enable_feature(snek),
     ?assertEqual([snek], config:features()),
     with_process_restart(config),
+    ?assert(config:is_enabled(snek)),
     ?assertEqual([snek], config:features()).
 
 should_notify_on_config_reload(Subscription, {_Apps, Pid}) ->
diff --git a/src/couch/src/couch_hash.erl b/src/couch/src/couch_hash.erl
index 842b37423..a2b3da4d8 100644
--- a/src/couch/src/couch_hash.erl
+++ b/src/couch/src/couch_hash.erl
@@ -10,10 +10,20 @@
 % License for the specific language governing permissions and limitations under
 % the License.
 
+% This module is enable use of the built-in Erlang MD5 hashing function for
+% non-cryptographic usage when in FIPS mode.
+%
+% For more details see:
+%   https://www.erlang.org/doc/apps/crypto/fips.html#avoid-md5-for-hashing
+
 -module(couch_hash).
 
 -export([md5_hash/1, md5_hash_final/1, md5_hash_init/0, md5_hash_update/2]).
 
+% The ERLANG_MD5 define is set at compile time by --erlang-md5 configure flag
+% This is deprecated. Instead, FIPS mode is now detected automatically and the
+% build-in Erlang function will be used when FIPS mode is enabled.
+%
 -ifdef(ERLANG_MD5).
 
 md5_hash(Data) ->
@@ -31,15 +41,27 @@ md5_hash_update(Context, Data) ->
 -else.
 
 md5_hash(Data) ->
-    crypto:hash(md5, Data).
+    case config:is_enabled(fips) of
+        true -> erlang:md5(Data);
+        false -> crypto:hash(md5, Data)
+    end.
 
 md5_hash_final(Context) ->
-    crypto:hash_final(Context).
+    case config:is_enabled(fips) of
+        true -> erlang:md5_final(Context);
+        false -> crypto:hash_final(Context)
+    end.
 
 md5_hash_init() ->
-    crypto:hash_init(md5).
+    case config:is_enabled(fips) of
+        true -> erlang:md5_init();
+        false -> crypto:hash_init(md5)
+    end.
 
 md5_hash_update(Context, Data) ->
-    crypto:hash_update(Context, Data).
+    case config:is_enabled(fips) of
+        true -> erlang:md5_update(Context, Data);
+        false -> crypto:hash_update(Context, Data)
+    end.
 
 -endif.
diff --git a/src/couch/src/couch_server.erl b/src/couch/src/couch_server.erl
index 4af4c1ff4..7dbbe4af1 100644
--- a/src/couch/src/couch_server.erl
+++ b/src/couch/src/couch_server.erl
@@ -274,14 +274,6 @@ init([N]) ->
     % Mark being able to receive documents with an _access property as a supported feature
     config:enable_feature('access-ready'),
 
-    % Mark if fips is enabled
-    case crypto:info_fips() == enabled of
-        true ->
-            config:enable_feature('fips');
-        false ->
-            ok
-    end,
-
     % read config and register for configuration changes
 
     % just stop if one of the config settings change. couch_server_sup
diff --git a/src/couch/test/eunit/couch_hash_test.erl b/src/couch/test/eunit/couch_hash_test.erl
new file mode 100644
index 000000000..a33164c6e
--- /dev/null
+++ b/src/couch/test/eunit/couch_hash_test.erl
@@ -0,0 +1,52 @@
+% 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.
+
+-module(couch_hash_test).
+
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(XY_HASH, <<62, 68, 16, 113, 112, 165, 32, 88, 42, 222, 82, 47, 167, 60, 29, 21>>).
+
+couch_hash_test_() ->
+    {
+        foreach,
+        fun setup/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_fips_disabled),
+            ?TDEF_FE(t_fips_enabled)
+        ]
+    }.
+
+setup() ->
+    Ctx = test_util:start_couch([crypto]),
+    config:disable_feature(fips),
+    Ctx.
+
+teardown(Ctx) ->
+    config:disable_feature(fips),
+    test_util:stop_couch(Ctx).
+
+t_fips_disabled(_) ->
+    ?assertEqual(?XY_HASH, couch_hash:md5_hash(<<"xy">>)),
+    H = couch_hash:md5_hash_init(),
+    H1 = couch_hash:md5_hash_update(H, <<"x">>),
+    H2 = couch_hash:md5_hash_update(H1, <<"y">>),
+    ?assertEqual(?XY_HASH, couch_hash:md5_hash_final(H2)).
+
+t_fips_enabled(_) ->
+    config:enable_feature(fips),
+    ?assertEqual(?XY_HASH, couch_hash:md5_hash(<<"xy">>)),
+    H = couch_hash:md5_hash_init(),
+    H1 = couch_hash:md5_hash_update(H, <<"x">>),
+    H2 = couch_hash:md5_hash_update(H1, <<"y">>),
+    ?assertEqual(?XY_HASH, couch_hash:md5_hash_final(H2)).