summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@gmail.com>2023-02-24 18:16:19 -0500
committerNick Vatamaniuc <nickva@users.noreply.github.com>2023-02-27 15:34:30 -0500
commit54879f9a5d093b8000d64070e7de323e155f2a2a (patch)
treeba09118fcd04b228e78e2053746cca64af175611
parentf677dd5e8f5276c5dff8d48b4df5494d74df4748 (diff)
downloadcouchdb-54879f9a5d093b8000d64070e7de323e155f2a2a.tar.gz
This enables configuring FIPS mode at runtime without the need for a custom build.
Issue: #4442
-rw-r--r--rel/overlay/etc/vm.args11
-rw-r--r--src/config/src/config.erl12
-rw-r--r--src/config/test/config_tests.erl6
-rw-r--r--src/couch/src/couch_hash.erl30
-rw-r--r--src/couch/src/couch_server.erl8
-rw-r--r--src/couch/test/eunit/couch_hash_test.erl52
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)).