Skip to content

Commit

Permalink
This enables configuring FIPS mode at runtime without the need for a …
Browse files Browse the repository at this point in the history
…custom build.

Issue: #4442
  • Loading branch information
nickva committed Feb 27, 2023
1 parent f677dd5 commit 54879f9
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 12 deletions.
11 changes: 11 additions & 0 deletions rel/overlay/etc/vm.args
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 12 additions & 0 deletions src/config/src/config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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) ->
Expand Down
6 changes: 6 additions & 0 deletions src/config/test/config_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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() ->
Expand All @@ -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() ->
Expand All @@ -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}) ->
Expand Down
30 changes: 26 additions & 4 deletions src/couch/src/couch_hash.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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) ->
Expand All @@ -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.
8 changes: 0 additions & 8 deletions src/couch/src/couch_server.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions src/couch/test/eunit/couch_hash_test.erl
Original file line number Diff line number Diff line change
@@ -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)).

0 comments on commit 54879f9

Please sign in to comment.