]> granicus.if.org Git - ejabberd/commitdiff
CVE-2016-1232: Add Dialback Key Generation and Validation support (XEP-0185)
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Mon, 11 Jan 2016 11:22:17 +0000 (14:22 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Mon, 11 Jan 2016 11:22:17 +0000 (14:22 +0300)
src/ejabberd_s2s.erl
src/ejabberd_s2s_in.erl
src/ejabberd_s2s_out.erl

index 0e51ec0448dd36893cc3178e1cdbb474fac2e40c..24694f3f0e8a729727e3141e1470e71cd676df32 100644 (file)
@@ -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] ->
+         mnesia:transform_table(s2s, ignore, [fromto, pid]),
          mnesia:clear_table(s2s);
-      [fromto, pid, key] -> ok;
+      [fromto, pid] -> ok;
       {'EXIT', _} -> ok
     end,
     case lists:member(local_s2s, mnesia:system_info(tables)) of
index 1b184a274df9ae73bf576ce9882127fde6429522..4159dee4ab6ccee8d0390cf47cec8905b926c8bb 100644 (file)
@@ -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">>,
index a5f7280555df2226f3a12fa2af64d138d165f356..331f54436581578b608ef51239408fed652f370a 100644 (file)
@@ -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 =