Skip to content

Commit 1d43a17

Browse files
authored
Merge pull request #4391 from esl/MIM-2314-reject-verify_peer-without-cacertfile
Mim 2314 reject verify peer without cacertfile
2 parents de22c49 + 9eef160 commit 1d43a17

File tree

5 files changed

+104
-14
lines changed

5 files changed

+104
-14
lines changed

rel/mim1.vars-toml.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
shaper = \"c2s_shaper\"
6969
max_stanza_size = 65536
7070
tls.certfile = \"priv/ssl/fake_server.pem\"
71+
tls.cacertfile = \"priv/ssl/cacert.pem\"
7172
tls.mode = \"tls\""}.
7273
{listen_service,
7374
"[[listen.service]]

src/config/mongoose_config_spec.erl

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
tls/2]).
1010

1111
%% callbacks for the 'process' step
12-
-export([process_root/1,
12+
-export([process_dynamic_domains/1,
13+
process_s2s/1,
1314
process_host/1,
1415
process_general/1,
1516
process_listener/2,
1617
process_c2s_tls/1,
1718
process_c2s_just_tls/1,
19+
process_c2s_fast_tls/1,
1820
process_just_tls/1,
1921
process_fast_tls/1,
2022
process_sasl_external/1,
@@ -106,7 +108,7 @@ root() ->
106108
},
107109
defaults = #{<<"internal_databases">> => default_internal_databases()},
108110
required = [<<"general">>],
109-
process = fun ?MODULE:process_root/1,
111+
process = [fun ?MODULE:process_dynamic_domains/1, fun ?MODULE:process_s2s/1],
110112
wrap = none,
111113
format_items = list
112114
}.
@@ -701,7 +703,7 @@ tls(c2s, just_tls) ->
701703
validate = filename}}},
702704
process = fun ?MODULE:process_c2s_just_tls/1};
703705
tls(c2s, fast_tls) ->
704-
#section{}.
706+
#section{process = fun ?MODULE:process_c2s_fast_tls/1}.
705707

706708
server_name_indication() ->
707709
#section{items = #{<<"enabled">> => #option{type = boolean},
@@ -938,8 +940,9 @@ s2s_address() ->
938940

939941
%% Callbacks for 'process'
940942

943+
%% Callback for root level `process` function
941944
%% Check that all auth methods and modules enabled for any host type support dynamic domains
942-
process_root(Items) ->
945+
process_dynamic_domains(Items) ->
943946
case proplists:lookup(host_types, Items) of
944947
{_, [_|_] = HostTypes} ->
945948
HTItems = lists:filter(fun(Item) -> is_host_type_item(Item, HostTypes) end, Items),
@@ -975,6 +978,48 @@ extract_modules(KVs) ->
975978
(_) -> []
976979
end, KVs)).
977980

981+
%% Callback for root level `process` function
982+
%% Ensure that CA Certificate file (`cacertfile` option) is provided for s2s listeners
983+
%% if user configured s2s `use_starttls` as `required` or `required_trusted`
984+
%% for at least one host, host_type or globally.
985+
process_s2s(Items) ->
986+
UseStartTlsValues = lists:flatmap(fun({{s2s, _}, S2S}) -> [maps:get(use_starttls, S2S)];
987+
(_) -> [] end, Items),
988+
case lists:any(fun is_starttls_required/1, UseStartTlsValues) of
989+
true ->
990+
check_s2s_verify_mode_cacertfile(Items);
991+
_ ->
992+
Items
993+
end.
994+
995+
is_starttls_required(required) ->
996+
true;
997+
is_starttls_required(required_trusted) ->
998+
true;
999+
is_starttls_required(_) ->
1000+
false.
1001+
1002+
check_s2s_verify_mode_cacertfile(Items) ->
1003+
case lists:keyfind(listen, 1, Items) of
1004+
false ->
1005+
Items;
1006+
{_, ListenItems} ->
1007+
S2S = [Item || Item <- ListenItems, maps:get(module, Item) == ejabberd_s2s_in],
1008+
lists:foreach(fun verify_s2s_tls/1, S2S),
1009+
Items
1010+
end.
1011+
1012+
verify_s2s_tls(#{tls := #{cacertfile := _}} = Item) ->
1013+
Item;
1014+
verify_s2s_tls(#{tls := #{verify_mode := none}} = Item) ->
1015+
Item;
1016+
verify_s2s_tls(_) ->
1017+
error(#{what => missing_cacertfile,
1018+
text => <<"You need to provide CA certificate (cacertfile) "
1019+
"or disable peer verification (set `verify_mode` to `none`) "
1020+
"in the `s2s.listen` sections when any of the s2s sections "
1021+
"contains use_starttls as `required` or `required_trusted`.">>}).
1022+
9781023
is_host_type_item({{_, HostType}, _}, HostTypes) ->
9791024
HostType =:= global orelse lists:member(HostType, HostTypes);
9801025
is_host_type_item(_, _) ->
@@ -1045,6 +1090,17 @@ fast_tls_defaults() ->
10451090
#{ciphers => mongoose_tls:default_ciphers(),
10461091
protocol_options => ["no_sslv2", "no_sslv3", "no_tlsv1", "no_tlsv1_1"]}.
10471092

1093+
process_c2s_fast_tls(M = #{module := just_tls}) ->
1094+
M;
1095+
process_c2s_fast_tls(M = #{cacertfile := _}) ->
1096+
M;
1097+
process_c2s_fast_tls(M = #{verify_mode := none}) ->
1098+
M;
1099+
process_c2s_fast_tls(_) ->
1100+
error(#{what => missing_cacertfile,
1101+
text => <<"You need to provide CA certificate (cacertfile) "
1102+
"or disable peer verification (verify_mode)">>}).
1103+
10481104
process_listener([item, Type | _], Opts) ->
10491105
mongoose_listener_config:ensure_ip_options(Opts#{module => listener_module(Type),
10501106
connection_type => connection_type(Type)}).

test/common/config_parser_helper.erl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ options("mongooseim-pgsql") ->
156156
access => c2s,
157157
shaper => c2s_shaper,
158158
max_stanza_size => 65536,
159-
tls => #{certfile => "priv/dc1.pem", dhfile => "priv/dh.pem"}
159+
tls => #{certfile => "priv/dc1.pem", dhfile => "priv/dh.pem",
160+
cacertfile => "priv/ca.pem"}
160161
}),
161162
config([listen, c2s],
162163
#{port => 5223,

test/config_parser_SUITE.erl

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ groups() ->
9494
listen_c2s_just_tls,
9595
listen_s2s,
9696
listen_s2s_tls,
97+
listen_s2s_cacertfile_verify,
9798
listen_service,
9899
listen_http,
99100
listen_http_tls,
@@ -508,11 +509,18 @@ listen_c2s_fast_tls(_Config) ->
508509
T = fun(Opts) -> listen_raw(c2s, #{<<"port">> => 5222,
509510
<<"tls">> => Opts}) end,
510511
P = [listen, 1, tls],
511-
?cfg(P, default_c2s_tls(fast_tls), T(#{})),
512+
M = tls_ca_raw(),
513+
?cfg(P, maps:merge(default_c2s_tls(fast_tls), tls_ca()), T(M)),
512514
test_fast_tls_server(P, T),
513-
?cfg(P ++ [mode], tls, T(#{<<"mode">> => <<"tls">>})),
514-
?err(T(#{<<"mode">> => <<"stopttls">>})),
515-
?err(T(#{<<"module">> => <<"slow_tls">>})).
515+
%% we do not require `cacertfile` when `verify_mode` is `none`
516+
?cfg(P ++ [verify_mode], none, T(#{<<"verify_mode">> => <<"none">>})),
517+
%% we require `cacertfile` when `verify_mode` is `peer` (which is the default)
518+
?cfg(P ++ [cacertfile], "priv/ca.pem", T(M#{<<"verify_mode">> => <<"peer">>})),
519+
?err([#{reason := missing_cacertfile}], T(#{})),
520+
?err([#{reason := missing_cacertfile}], T(#{<<"verify_mode">> => <<"peer">>})),
521+
?cfg(P ++ [mode], tls, T(M#{<<"mode">> => <<"tls">>})),
522+
?err(T(M#{<<"mode">> => <<"stopttls">>})),
523+
?err(T(M#{<<"module">> => <<"slow_tls">>})).
516524

517525
listen_c2s_just_tls(_Config) ->
518526
T = fun(Opts) -> listen_raw(c2s, #{<<"port">> => 5222,
@@ -545,6 +553,28 @@ listen_s2s_tls(_Config) ->
545553
?cfg(P, default_config([listen, s2s, tls]), T(#{})),
546554
test_fast_tls_server(P, T).
547555

556+
listen_s2s_cacertfile_verify(_Config) ->
557+
T = fun(UseStartTLS, Opts) ->
558+
maps:merge(#{<<"s2s">> => #{<<"use_starttls">> => UseStartTLS}},
559+
listen_raw(s2s, #{<<"port">> => 5269, <<"tls">> => Opts})) end,
560+
P = [listen, 1, tls],
561+
ConfigWithCA = maps:merge(default_config([listen, s2s, tls]), tls_ca()),
562+
%% no checking of `cacertfile` when `use_starttls` is `false` or `optional`
563+
?cfg(P, default_config([listen, s2s, tls]), T(<<"false">>, #{})),
564+
?cfg(P, default_config([listen, s2s, tls]), T(<<"optional">>, #{})),
565+
%% `cacertfile` is required when `use_starttls` is `required` or `optional`
566+
?cfg(P, ConfigWithCA, T(<<"required">>, tls_ca_raw())),
567+
?cfg(P, ConfigWithCA, T(<<"required_trusted">>, tls_ca_raw())),
568+
?err([#{reason := missing_cacertfile}], T(<<"required">>, #{})),
569+
?err([#{reason := missing_cacertfile}], T(<<"required_trusted">>, #{})),
570+
%% setting `verify_mode` to `none` turns off `cacertfile` validation
571+
VerifyModeNone = #{verify_mode => none},
572+
VerifyModeNoneRaw = #{<<"verify_mode">> => <<"none">>},
573+
ConfigWithVerifyModeNone = maps:merge(default_config([listen, s2s, tls]),
574+
#{verify_mode => none}),
575+
?cfg(P, ConfigWithVerifyModeNone, T(<<"required">>, VerifyModeNoneRaw)),
576+
?cfg(P, ConfigWithVerifyModeNone, T(<<"required_trusted">>, VerifyModeNoneRaw)).
577+
548578
listen_service(_Config) ->
549579
T = fun(Opts) -> listen_raw(service, maps:merge(#{<<"port">> => 8888,
550580
<<"password">> => <<"secret">>}, Opts))
@@ -1197,12 +1227,13 @@ test_just_tls_client_sni(ParentP, ParentT) ->
11971227

11981228
test_fast_tls_server(P, T) ->
11991229
?cfg(P ++ [verify_mode], none, T(#{<<"verify_mode">> => <<"none">>})),
1200-
?cfg(P ++ [certfile], "priv/cert.pem", T(#{<<"certfile">> => <<"priv/cert.pem">>})),
1201-
?cfg(P ++ [cacertfile], "priv/ca.pem", T(tls_ca_raw())),
1230+
M = tls_ca_raw(),
1231+
?cfg(P ++ [certfile], "priv/cert.pem", T(M#{<<"certfile">> => <<"priv/cert.pem">>})),
1232+
?cfg(P ++ [cacertfile], "priv/ca.pem", T(M)),
12021233
?cfg(P ++ [ciphers], "TLS_AES_256_GCM_SHA384",
1203-
T(#{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>})),
1204-
?cfg(P ++ [dhfile], "priv/dh.pem", T(#{<<"dhfile">> => <<"priv/dh.pem">>})),
1205-
?cfg(P ++ [protocol_options], ["nosslv2"], T(#{<<"protocol_options">> => [<<"nosslv2">>]})),
1234+
T(M#{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>})),
1235+
?cfg(P ++ [dhfile], "priv/dh.pem", T(M#{<<"dhfile">> => <<"priv/dh.pem">>})),
1236+
?cfg(P ++ [protocol_options], ["nosslv2"], T(M#{<<"protocol_options">> => [<<"nosslv2">>]})),
12061237
?err(T(#{<<"verify_mode">> => <<"selfsigned_peer">>})), % value only for just_tls
12071238
?err(T(#{<<"crl_files">> => [<<"priv/cert.pem">>]})), % option only for just_tls
12081239
?err(T(#{<<"certfile">> => <<"no_such_file.pem">>})),

test/config_parser_SUITE_data/mongooseim-pgsql.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
tls.mode = "starttls"
8787
tls.certfile = "priv/dc1.pem"
8888
tls.dhfile = "priv/dh.pem"
89+
tls.cacertfile = "priv/ca.pem"
8990

9091
[[listen.c2s]]
9192
port = 5223

0 commit comments

Comments
 (0)