From c7931b4a4f35022325feaa58bb555bb688c90bd2 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 11 Jan 2016 14:22:17 +0300 Subject: [PATCH] CVE-2016-1232: Add Dialback Key Generation and Validation support (XEP-0185) --- src/ejabberd_s2s.erl | 61 ++++++++++++++++------------------------ src/ejabberd_s2s_in.erl | 6 ++-- src/ejabberd_s2s_out.erl | 32 +++++++++++---------- 3 files changed, 44 insertions(+), 55 deletions(-) diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index 0e51ec044..24694f3f0 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -35,8 +35,8 @@ %% API -export([start_link/0, route/3, have_connection/1, - has_key/2, get_connections_pids/1, try_register/1, - remove_connection/3, find_connection/2, + make_key/2, get_connections_pids/1, try_register/1, + remove_connection/2, find_connection/2, dirty_get_connections/0, allow_host/2, incoming_s2s_number/0, outgoing_s2s_number/0, clean_temporarily_blocked_table/0, @@ -75,8 +75,7 @@ %% once a server is temporarly blocked, it stay blocked for 60 seconds -record(s2s, {fromto = {<<"">>, <<"">>} :: {binary(), binary()} | '_', - pid = self() :: pid() | '_' | '$1', - key = <<"">> :: binary() | '_'}). + pid = self() :: pid() | '_' | '$1'}). -record(state, {}). @@ -134,19 +133,15 @@ is_temporarly_blocked(Host) -> end. -spec remove_connection({binary(), binary()}, - pid(), binary()) -> {atomic, ok} | - ok | - {aborted, any()}. + pid()) -> {atomic, ok} | ok | {aborted, any()}. -remove_connection(FromTo, Pid, Key) -> +remove_connection(FromTo, Pid) -> case catch mnesia:dirty_match_object(s2s, - #s2s{fromto = FromTo, pid = Pid, - _ = '_'}) + #s2s{fromto = FromTo, pid = Pid}) of - [#s2s{pid = Pid, key = Key}] -> + [#s2s{pid = Pid}] -> F = fun () -> - mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid, - key = Key}) + mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid}) end, mnesia:transaction(F); _ -> ok @@ -162,19 +157,6 @@ have_connection(FromTo) -> false end. --spec has_key({binary(), binary()}, binary()) -> boolean(). - -has_key(FromTo, Key) -> - case mnesia:dirty_select(s2s, - [{#s2s{fromto = FromTo, key = Key, _ = '_'}, - [], - ['$_']}]) of - [] -> - false; - _ -> - true - end. - -spec get_connections_pids({binary(), binary()}) -> [pid()]. get_connections_pids(FromTo) -> @@ -185,10 +167,9 @@ get_connections_pids(FromTo) -> [] end. --spec try_register({binary(), binary()}) -> {key, binary()} | false. +-spec try_register({binary(), binary()}) -> boolean(). try_register(FromTo) -> - Key = randoms:get_string(), MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo), MaxS2SConnectionsNumberPerNode = max_s2s_connections_number_per_node(FromTo), @@ -198,9 +179,8 @@ try_register(FromTo) -> MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode), if NeededConnections > 0 -> - mnesia:write(#s2s{fromto = FromTo, pid = self(), - key = Key}), - {key, Key}; + mnesia:write(#s2s{fromto = FromTo, pid = self()}), + true; true -> false end end, @@ -241,6 +221,12 @@ check_peer_certificate(SockMod, Sock, Peer) -> {error, <<"Cannot get peer certificate">>} end. +make_key({From, To}, StreamID) -> + Secret = ejabberd_config:get_option(shared_key, fun(V) -> V end), + p1_sha:to_hexlist( + crypto:hmac(sha256, p1_sha:to_hexlist(crypto:hash(sha256, Secret)), + [To, " ", From, " ", StreamID])). + %%==================================================================== %% gen_server callbacks %%==================================================================== @@ -407,17 +393,15 @@ open_several_connections(N, MyServer, Server, From, new_connection(MyServer, Server, From, FromTo, MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode) -> - Key = randoms:get_string(), {ok, Pid} = ejabberd_s2s_out:start( - MyServer, Server, {new, Key}), + MyServer, Server, new), F = fun() -> L = mnesia:read({s2s, FromTo}), NeededConnections = needed_connections_number(L, MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode), if NeededConnections > 0 -> - mnesia:write(#s2s{fromto = FromTo, pid = Pid, - key = Key}), + mnesia:write(#s2s{fromto = FromTo, pid = Pid}), ?INFO_MSG("New s2s connection started ~p", [Pid]), Pid; true -> choose_connection(From, L) @@ -520,9 +504,12 @@ update_tables() -> end, case catch mnesia:table_info(s2s, attributes) of [fromto, node, key] -> - mnesia:transform_table(s2s, ignore, [fromto, pid, key]), + mnesia:transform_table(s2s, ignore, [fromto, pid]), mnesia:clear_table(s2s); - [fromto, pid, key] -> ok; + [fromto, pid, key] -> + mnesia:transform_table(s2s, ignore, [fromto, pid]), + mnesia:clear_table(s2s); + [fromto, pid] -> ok; {'EXIT', _} -> ok end, case lists:member(local_s2s, mnesia:system_info(tables)) of diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 1b184a274..4159dee4a 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -432,9 +432,9 @@ stream_established({xmlstreamelement, El}, StateData) -> ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]), LTo = jid:nameprep(To), LFrom = jid:nameprep(From), - Type = case ejabberd_s2s:has_key({LTo, LFrom}, Key) of - true -> <<"valid">>; - _ -> <<"invalid">> + Type = case ejabberd_s2s:make_key({LTo, LFrom}, Id) of + Key -> <<"valid">>; + _ -> <<"invalid">> end, send_element(StateData, #xmlel{name = <<"db:verify">>, diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index a5f728055..331f54436 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -56,6 +56,7 @@ -record(state, {socket :: ejabberd_socket:socket_state(), streamid = <<"">> :: binary(), + remote_streamid = <<"">> :: binary(), use_v10 = true :: boolean(), tls = false :: boolean(), tls_required = false :: boolean(), @@ -69,7 +70,7 @@ server = <<"">> :: binary(), queue = queue:new() :: ?TQUEUE, delay_to_retry = undefined_delay :: undefined_delay | non_neg_integer(), - new = false :: false | binary(), + new = false :: boolean(), verify = false :: false | {pid(), binary(), binary()}, bridge :: {atom(), atom()}, timer = make_ref() :: reference()}). @@ -196,7 +197,7 @@ init([From, Server, Type]) -> true -> TLSOpts4 end, {New, Verify} = case Type of - {new, Key} -> {Key, false}; + new -> {true, false}; {verify, Pid, Key, SID} -> start_connection(self()), {false, {Pid, Key, SID}} end, @@ -310,7 +311,7 @@ open_socket2(Type, Addr, Port) -> wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> - {CertCheckRes, CertCheckMsg, NewStateData} = + {CertCheckRes, CertCheckMsg, StateData0} = if StateData#state.tls_certverify, StateData#state.tls_enabled -> {Res, Msg} = ejabberd_s2s:check_peer_certificate(ejabberd_socket, @@ -322,6 +323,8 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, true -> {no_verify, <<"Not verified">>, StateData} end, + RemoteStreamID = xml:get_attr_s(<<"id">>, Attrs), + NewStateData = StateData0#state{remote_streamid = RemoteStreamID}, case {xml:get_attr_s(<<"xmlns">>, Attrs), xml:get_attr_s(<<"xmlns:db">>, Attrs), xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>} @@ -958,10 +961,10 @@ terminate(Reason, StateName, StateData) -> ?DEBUG("terminated: ~p", [{Reason, StateName}]), case StateData#state.new of false -> ok; - Key -> + true -> ejabberd_s2s:remove_connection({StateData#state.myname, StateData#state.server}, - self(), Key) + self()) end, bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND), @@ -1029,19 +1032,18 @@ bounce_messages(Error) -> send_db_request(StateData) -> Server = StateData#state.server, New = case StateData#state.new of - false -> - case ejabberd_s2s:try_register({StateData#state.myname, - Server}) - of - {key, Key} -> Key; - false -> false - end; - Key -> Key + false -> + ejabberd_s2s:try_register({StateData#state.myname, Server}); + true -> + true end, NewStateData = StateData#state{new = New}, try case New of - false -> ok; - Key1 -> + false -> ok; + true -> + Key1 = ejabberd_s2s:make_key( + {StateData#state.myname, Server}, + StateData#state.remote_streamid), send_element(StateData, #xmlel{name = <<"db:result">>, attrs =