diff options
author | Nick Vatamaniuc <vatamane@gmail.com> | 2023-02-24 18:16:19 -0500 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2023-02-27 15:34:30 -0500 |
commit | 54879f9a5d093b8000d64070e7de323e155f2a2a (patch) | |
tree | ba09118fcd04b228e78e2053746cca64af175611 | |
parent | f677dd5e8f5276c5dff8d48b4df5494d74df4748 (diff) | |
download | couchdb-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.args | 11 | ||||
-rw-r--r-- | src/config/src/config.erl | 12 | ||||
-rw-r--r-- | src/config/test/config_tests.erl | 6 | ||||
-rw-r--r-- | src/couch/src/couch_hash.erl | 30 | ||||
-rw-r--r-- | src/couch/src/couch_server.erl | 8 | ||||
-rw-r--r-- | 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)). |