From ee67d46285394db95133709cef74b0c462d665aa Mon Sep 17 00:00:00 2001
From: Jakub Witczak <kuba@erlang.org>
Date: Fri, 15 Dec 2023 09:12:33 +0100
Subject: [PATCH] ssh: KEX strict

Origin: https://github.com/erlang/otp/commit/ee67d46285394db95133709cef74b0c462d665aa

- negotiate "strict KEX" OpenSSH feature
- when negotiated between peers apply strict KEX
- related tests
- print_seqnums fix in ssh_trtp test code

---
 lib/ssh/src/ssh.hrl                    |   5 +-
 lib/ssh/src/ssh_connection_handler.erl |  12 ++-
 lib/ssh/src/ssh_transport.erl          | 101 ++++++++++++++++++++-----
 lib/ssh/src/ssh_transport.hrl          |   4 +-
 lib/ssh/test/ssh_protocol_SUITE.erl    |  97 +++++++++++++++++++++---
 lib/ssh/test/ssh_trpt_test_lib.erl     |  34 +++++----
 6 files changed, 207 insertions(+), 46 deletions(-)

diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index 923e930..1f6c390 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -385,6 +385,8 @@
           send_ext_info, %% May send ext-info to peer
           recv_ext_info, %% Expect ext-info from peer
 
+          kex_strict_negotiated = false,
+
 	  algorithms,   %% #alg{}
 	  
 	  send_mac = none, %% send MAC algorithm
@@ -454,7 +456,8 @@
 	  c_lng,
 	  s_lng,
           send_ext_info,
-          recv_ext_info
+          recv_ext_info,
+          kex_strict_negotiated = false
 	 }).
 
 -record(ssh_key,
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 7c87591..88ee620 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -640,7 +640,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, D0) ->
 handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg},
 	     D = #data{key_exchange_init_msg = OwnKex}) ->
     Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload),
-    Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of
+    Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1, ReNeg) of
 	      {ok, NextKexMsg, Ssh2} when Role==client ->
 		  send_bytes(NextKexMsg, D),
 		  Ssh2;
@@ -1000,6 +1000,16 @@ handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) ->
     disconnect_fun("Received disconnect: "++Desc, D),
     {stop_and_reply, {shutdown,Desc}, Actions, D};
 
+handle_event(internal, #ssh_msg_ignore{}, {_StateName, _Role, init},
+             #data{ssh_params = #ssh{kex_strict_negotiated = true,
+                                     send_sequence = SendSeq,
+                                     recv_sequence = RecvSeq}}) ->
+    ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+                io_lib:format("strict KEX violation: unexpected SSH_MSG_IGNORE "
+                              "send_sequence = ~p  recv_sequence = ~p",
+                              [SendSeq, RecvSeq])
+               );
+
 handle_event(_, #ssh_msg_ignore{}, _, _) ->
     keep_state_and_data;
 
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 9ff2045..a001f86 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -41,7 +41,7 @@
 	 key_exchange_init_msg/1,
 	 key_init/3, new_keys_message/1,
          ext_info_message/1,
-	 handle_kexinit_msg/3, handle_kexdh_init/2,
+	 handle_kexinit_msg/4, handle_kexdh_init/2,
 	 handle_kex_dh_gex_group/2, handle_kex_dh_gex_init/2, handle_kex_dh_gex_reply/2,
 	 handle_new_keys/2, handle_kex_dh_gex_request/2,
 	 handle_kexdh_reply/2, 
@@ -161,7 +161,6 @@ supported_algorithms(cipher) ->
     same(
       select_crypto_supported(
 	[
-         {'chacha20-poly1305@openssh.com', [{ciphers,chacha20}, {macs,poly1305}]},
          {'aes256-gcm@openssh.com', [{ciphers,{aes_gcm,256}}]},
          {'aes256-ctr',       [{ciphers,{aes_ctr,256}}]},
          {'aes192-ctr',       [{ciphers,{aes_ctr,192}}]},
@@ -169,6 +168,7 @@ supported_algorithms(cipher) ->
 	 {'aes128-ctr',       [{ciphers,{aes_ctr,128}}]},
 	 {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]},
 	 {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]},
+         {'chacha20-poly1305@openssh.com', [{ciphers,chacha20}, {macs,poly1305}]},
 	 {'aes128-cbc',       [{ciphers,aes_cbc128}]},
 	 {'3des-cbc',         [{ciphers,des3_cbc}]}
 	]
@@ -295,7 +295,8 @@ kexinit_message(Role, Random, Algs, HostKeyAlgs, Opts) ->
     #ssh_msg_kexinit{
 		  cookie = Random,
 		  kex_algorithms = to_strings( get_algs(kex,Algs) )
-                                   ++ kex_ext_info(Role,Opts),
+                                   ++ kex_ext_info(Role,Opts)
+                                   ++ kex_strict_alg(Role),
 		  server_host_key_algorithms = HostKeyAlgs,
 		  encryption_algorithms_client_to_server = c2s(cipher,Algs),
 		  encryption_algorithms_server_to_client = s2c(cipher,Algs),
@@ -323,10 +324,12 @@ new_keys_message(Ssh0) ->
 
 
 handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
-                   #ssh{role = client} = Ssh) ->
+                   #ssh{role = client} = Ssh, ReNeg) ->
     try
-        {ok, Algorithms} = select_algorithm(client, Own, CounterPart, Ssh#ssh.opts),
+        {ok, Algorithms} =
+            select_algorithm(client, Own, CounterPart, Ssh, ReNeg),
         true = verify_algorithm(Algorithms),
+        true = verify_kexinit_is_first_msg(Algorithms, Ssh, ReNeg),
         Algorithms
     of
 	Algos ->
@@ -341,10 +344,11 @@ handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
     end;
 
 handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
-                   #ssh{role = server} = Ssh) ->
+                   #ssh{role = server} = Ssh, ReNeg) ->
     try
-        {ok, Algorithms} = select_algorithm(server, CounterPart, Own, Ssh#ssh.opts),
+        {ok, Algorithms} = select_algorithm(server, CounterPart, Own, Ssh, ReNeg),
         true = verify_algorithm(Algorithms),
+        true = verify_kexinit_is_first_msg(Algorithms, Ssh, ReNeg),
         Algorithms
     of
 	Algos ->
@@ -374,6 +378,21 @@ verify_algorithm(#alg{kex = Kex}) ->
         false -> {false, "kex"}
     end.
 
+verify_kexinit_is_first_msg(#alg{kex_strict_negotiated = false}, _, _) ->
+    true;
+verify_kexinit_is_first_msg(#alg{kex_strict_negotiated = true}, _, renegotiate) ->
+    true;
+verify_kexinit_is_first_msg(#alg{kex_strict_negotiated = true},
+                            #ssh{send_sequence = 1, recv_sequence = 1},
+                            init) ->
+    true;
+verify_kexinit_is_first_msg(#alg{kex_strict_negotiated = true},
+                            #ssh{send_sequence = SendSequence,
+                                 recv_sequence = RecvSequence}, init) ->
+    error_logger:warning_report(
+      lists:concat(["KEX strict violation (", SendSequence, ", ", RecvSequence, ")."])),
+    {false, "kex_strict"}.
+
 %%%----------------------------------------------------------------
 %%%
 %%% Key exchange initialization
@@ -736,6 +755,9 @@ handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
                        )
     end. 
 
+%%%----------------------------------------------------------------
+kex_strict_alg(client) -> [?kex_strict_c];
+kex_strict_alg(server) -> [?kex_strict_s].
 
 %%%----------------------------------------------------------------
 kex_ext_info(Role, Opts) ->
@@ -908,7 +930,35 @@ known_host_key(#ssh{opts = Opts, peer = {PeerName,_}} = Ssh,
 %%
 %%   The first algorithm in each list MUST be the preferred (guessed)
 %%   algorithm.  Each string MUST contain at least one algorithm name.
-select_algorithm(Role, Client, Server, Opts) ->
+select_algorithm(Role, Client, Server,
+                 #ssh{opts = Opts,
+                         kex_strict_negotiated = KexStrictNegotiated0},
+                 ReNeg) ->
+    KexStrictNegotiated =
+        case ReNeg of
+            %% KEX strict negotiated once per connection
+            init ->
+                Result =
+                    case Role of
+                        server ->
+                            lists:member(?kex_strict_c,
+                                         Client#ssh_msg_kexinit.kex_algorithms);
+                        client ->
+                            lists:member(?kex_strict_s,
+                                         Server#ssh_msg_kexinit.kex_algorithms)
+                    end,
+                case Result of
+                    true ->
+                        error_logger:info_report(
+                          lists:concat([Role, " will use strict KEX ordering"]));
+                    _ ->
+                        ok
+                end,
+                Result;
+            _ ->
+                KexStrictNegotiated0
+        end,
+
     {Encrypt0, Decrypt0} = select_encrypt_decrypt(Role, Client, Server),
     {SendMac0, RecvMac0} = select_send_recv_mac(Role, Client, Server),
 
@@ -959,7 +1009,8 @@ select_algorithm(Role, Client, Server, Opts) ->
               c_lng = C_Lng,
               s_lng = S_Lng,
               send_ext_info = SendExtInfo,
-              recv_ext_info = RecvExtInfo
+              recv_ext_info = RecvExtInfo,
+              kex_strict_negotiated = KexStrictNegotiated
              }}.
 
 
@@ -1057,7 +1108,8 @@ alg_setup(snd, SSH) ->
 	    c_lng = ALG#alg.c_lng,
 	    s_lng = ALG#alg.s_lng,
             send_ext_info = ALG#alg.send_ext_info,
-            recv_ext_info = ALG#alg.recv_ext_info
+            recv_ext_info = ALG#alg.recv_ext_info,
+            kex_strict_negotiated = ALG#alg.kex_strict_negotiated
 	   };
 
 alg_setup(rcv, SSH) ->
@@ -1069,22 +1121,23 @@ alg_setup(rcv, SSH) ->
 	    c_lng = ALG#alg.c_lng,
 	    s_lng = ALG#alg.s_lng,
             send_ext_info = ALG#alg.send_ext_info,
-            recv_ext_info = ALG#alg.recv_ext_info
+            recv_ext_info = ALG#alg.recv_ext_info,
+            kex_strict_negotiated = ALG#alg.kex_strict_negotiated
 	   }.
 
-
-alg_init(snd, SSH0) ->
+alg_init(Dir = snd, SSH0) ->
     {ok,SSH1} = send_mac_init(SSH0),
     {ok,SSH2} = encrypt_init(SSH1),
     {ok,SSH3} = compress_init(SSH2),
-    SSH3;
+    {ok,SSH4} = maybe_reset_sequence(Dir, SSH3),
+    SSH4;
 
-alg_init(rcv, SSH0) ->
+alg_init(Dir = rcv, SSH0) ->
     {ok,SSH1} = recv_mac_init(SSH0),
     {ok,SSH2} = decrypt_init(SSH1),
     {ok,SSH3} = decompress_init(SSH2),
-    SSH3.
-
+    {ok,SSH4} = maybe_reset_sequence(Dir, SSH3),
+    SSH4.
 
 alg_final(snd, SSH0) ->
     {ok,SSH1} = send_mac_final(SSH0),
@@ -2127,6 +2180,14 @@ len_supported(Name, Len) ->
 
 same(Algs) ->  [{client2server,Algs}, {server2client,Algs}].
 
+maybe_reset_sequence(snd, Ssh = #ssh{kex_strict_negotiated = true}) ->
+    {ok, Ssh#ssh{send_sequence = 0}};
+maybe_reset_sequence(rcv, Ssh = #ssh{kex_strict_negotiated = true}) ->
+    {ok, Ssh#ssh{recv_sequence = 0}};
+maybe_reset_sequence(_Dir, Ssh) ->
+    {ok, Ssh}.
+
+
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %%
 %% Other utils
@@ -2169,8 +2230,8 @@ dbg_trace(C, raw_messages, A) -> dbg_trace(C, hello, A);
 dbg_trace(C, ssh_messages, A) -> dbg_trace(C, hello, A);
 
 dbg_trace(flags, alg,   _) -> [c];
-dbg_trace(on,    alg,   _) -> dbg:tpl(?MODULE,select_algorithm,4,x);
-dbg_trace(off,   alg,   _) -> dbg:ctpl(?MODULE,select_algorithm,4);
+dbg_trace(on,    alg,   _) -> dbg:tpl(?MODULE,select_algorithm,5,x);
+dbg_trace(off,   alg,   _) -> dbg:ctpl(?MODULE,select_algorithm,5);
 
 
 dbg_trace(format, hello, {return_from,{?MODULE,hello_version_msg,1},Hello}) ->
@@ -2182,7 +2243,7 @@ dbg_trace(format, hello, {call,{?MODULE,handle_hello_version,[Hello]}}) ->
      Hello
     ];
 
-dbg_trace(format, alg, {return_from,{?MODULE,select_algorithm,4},{ok,Alg}}) ->
+dbg_trace(format, alg, {return_from,{?MODULE,select_algorithm,5},{ok,Alg}}) ->
     ["Negotiated algorithms:\n",
      wr_record(Alg)
     ].
diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl
index f424a4f..59ac9db 100644
--- a/lib/ssh/src/ssh_transport.hrl
+++ b/lib/ssh/src/ssh_transport.hrl
@@ -266,5 +266,7 @@
 -define(dh_group18,
         {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF}).
 
-
+%%% OpenSSH KEX strict
+-define(kex_strict_c, "kex-strict-c-v00@openssh.com").
+-define(kex_strict_s, "kex-strict-s-v00@openssh.com").
 -endif. % -ifdef(ssh_transport).
diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl
index 3e3e151..0f3cfbc 100644
--- a/lib/ssh/test/ssh_protocol_SUITE.erl
+++ b/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -83,8 +83,10 @@ groups() ->
 		gex_client_init_option_groups_moduli_file,
 		gex_client_init_option_groups_file,
 		gex_client_old_request_exact,
-		gex_client_old_request_noexact
-		]},
+		gex_client_old_request_noexact,
+                kex_strict_negotiated,
+                kex_strict_msg_ignore,
+                kex_strict_msg_unknown]},
      {service_requests, [], [bad_service_name,
 			     bad_long_service_name,
 			     bad_very_long_service_name,
@@ -109,17 +111,16 @@ groups() ->
 
 init_per_suite(Config) ->
     ?CHECK_CRYPTO(start_std_daemon( setup_dirs( start_apps(Config)))).
-    
+
 end_per_suite(Config) ->
     stop_apps(Config).
 
-
-
 init_per_testcase(no_common_alg_server_disconnects, Config) ->
     start_std_daemon(Config, [{preferred_algorithms,[{public_key,['ssh-rsa']},
                                                      {cipher,?DEFAULT_CIPHERS}
                                                     ]}]);
-
+init_per_testcase(kex_strict_negotiated, Config) ->
+    Config;
 init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
 				   TC == gex_client_init_option_groups_moduli_file ;
 				   TC == gex_client_init_option_groups_file ;
@@ -162,6 +163,8 @@ init_per_testcase(_TestCase, Config) ->
 
 end_per_testcase(no_common_alg_server_disconnects, Config) ->
     stop_std_daemon(Config);
+end_per_testcase(kex_strict_negotiated, Config) ->
+    Config;
 end_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
 				  TC == gex_client_init_option_groups_moduli_file ;
 				  TC == gex_client_init_option_groups_file ;
@@ -763,6 +766,80 @@ ext_info_c(Config) ->
         {result, Pid, Error} -> ct:fail("Error: ~p",[Error])
     end.
 
+%%%--------------------------------------------------------------------
+%%%
+kex_strict_negotiated(Config0) ->
+    {ok,Pid} = ssh_test_lib:add_report_handler(),
+    Config = start_std_daemon(Config0, []),
+    {Server, Host, Port} = proplists:get_value(server, Config),
+    #{level := Level} = logger:get_primary_config(),
+    logger:set_primary_config(level, notice),
+    {ok, ConnRef} = std_connect({Host, Port}, Config, []),
+    {algorithms, A} = ssh:connection_info(ConnRef, algorithms),
+    ssh:stop_daemon(Server),
+    {ok, Reports} = ssh_test_lib:get_reports(Pid),
+    ct:log("Reports = ~p", [Reports]),
+    true = ssh_test_lib:kex_strict_negotiated(client, Reports),
+    true = ssh_test_lib:kex_strict_negotiated(server, Reports),
+    logger:set_primary_config(Level),
+    ok.
+
+%% Connect to an erlang server and inject unexpected SSH ignore
+kex_strict_msg_ignore(Config) ->
+    ct:log("START: ~p~n=================================", [?FUNCTION_NAME]),
+    ExpectedReason = "strict KEX violation: unexpected SSH_MSG_IGNORE",
+    TestMessages =
+        [{send, ssh_msg_ignore},
+         {match, #ssh_msg_kexdh_reply{_='_'}, receive_msg},
+         {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}],
+    kex_strict_helper(Config, TestMessages, ExpectedReason).
+
+%% Connect to an erlang server and inject unexpected non-SSH binary
+kex_strict_msg_unknown(Config) ->
+    ct:log("START: ~p~n=================================", [?FUNCTION_NAME]),
+    ExpectedReason = "Bad packet: Size",
+    TestMessages =
+        [{send, ssh_msg_unknown},
+         {match, #ssh_msg_kexdh_reply{_='_'}, receive_msg},
+         {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}],
+    kex_strict_helper(Config, TestMessages, ExpectedReason).
+
+kex_strict_helper(Config, TestMessages, ExpectedReason) ->
+    {ok,HandlerPid} = ssh_test_lib:add_report_handler(),
+    #{level := Level} = logger:get_primary_config(),
+    logger:set_primary_config(level, notice),
+    %% Connect and negotiate keys
+    {ok, InitialState} = ssh_trpt_test_lib:exec(
+			  [{set_options, [print_ops, print_seqnums, print_messages]}]
+			 ),
+    {ok, _AfterKexState} =
+        ssh_trpt_test_lib:exec(
+          [{connect,
+            server_host(Config),server_port(Config),
+            [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
+                                    {cipher,?DEFAULT_CIPHERS}
+                                   ]},
+             {silently_accept_hosts, true},
+             {recv_ext_info, false},
+             {user_dir, user_dir(Config)},
+             {user_interaction, false}
+            | proplists:get_value(extra_options,Config,[])
+            ]},
+           receive_hello,
+           {send, hello},
+           {send, ssh_msg_kexinit},
+           {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+           {send, ssh_msg_kexdh_init}] ++
+              TestMessages,
+          InitialState),
+    ct:sleep(100),
+    {ok, Reports} = ssh_test_lib:get_reports(HandlerPid),
+    ct:log("HandlerPid = ~p~nReports = ~p", [HandlerPid, Reports]),
+    true = ssh_test_lib:kex_strict_negotiated(client, Reports),
+    true = ssh_test_lib:kex_strict_negotiated(server, Reports),
+    true = ssh_test_lib:event_logged(server, Reports, ExpectedReason),
+    logger:set_primary_config(Level),
+    ok.
 
 %%%----------------------------------------------------------------
 %%%
@@ -784,7 +861,7 @@ modify_append(Config) ->
     Ciphers = filter_supported(cipher, ?CIPHERS),
     {ok,_} =
         chk_pref_algs(Config,
-                      [?DEFAULT_KEX, ?EXTRA_KEX],
+                      [?DEFAULT_KEX, ?EXTRA_KEX, list_to_atom(?kex_strict_s)],
                       Ciphers,
                       [{preferred_algorithms, [{kex,[?DEFAULT_KEX]},
                                                {cipher,Ciphers}
@@ -798,7 +875,7 @@ modify_prepend(Config) ->
     Ciphers = filter_supported(cipher, ?CIPHERS),
     {ok,_} =
         chk_pref_algs(Config,
-                      [?EXTRA_KEX, ?DEFAULT_KEX],
+                      [?EXTRA_KEX, ?DEFAULT_KEX, list_to_atom(?kex_strict_s)],
                       Ciphers,
                       [{preferred_algorithms, [{kex,[?DEFAULT_KEX]},
                                                {cipher,Ciphers}
@@ -812,7 +889,7 @@ modify_rm(Config) ->
     Ciphers = filter_supported(cipher, ?CIPHERS),
     {ok,_} =
         chk_pref_algs(Config,
-                      [?DEFAULT_KEX],
+                      [?DEFAULT_KEX, list_to_atom(?kex_strict_s)],
                       tl(Ciphers),
                       [{preferred_algorithms, [{kex,[?DEFAULT_KEX,?EXTRA_KEX]},
                                                {cipher,Ciphers}
@@ -831,7 +908,7 @@ modify_combo(Config) ->
     LastC = lists:last(Ciphers),
     {ok,_} =
         chk_pref_algs(Config,
-                      [?DEFAULT_KEX],
+                      [?DEFAULT_KEX, list_to_atom(?kex_strict_s)],
                       [LastC] ++ (tl(Ciphers)--[LastC]) ++ [hd(Ciphers)],
                       [{preferred_algorithms, [{kex,[?DEFAULT_KEX,?EXTRA_KEX]},
                                                {cipher,Ciphers}
diff --git a/lib/ssh/test/ssh_trpt_test_lib.erl b/lib/ssh/test/ssh_trpt_test_lib.erl
index f2c9892..1b60ea4 100644
--- a/lib/ssh/test/ssh_trpt_test_lib.erl
+++ b/lib/ssh/test/ssh_trpt_test_lib.erl
@@ -75,7 +75,7 @@ exec(L, S) when is_list(L) -> lists:foldl(fun exec/2, S, L);
 exec(Op, S0=#s{}) ->
     S1 = init_op_traces(Op, S0),
     try seqnum_trace(
-	  op(Op, S1))
+	  op(Op, S1), S1)
     of
 	S = #s{} ->
 	    case proplists:get_value(silent,S#s.opts) of
@@ -333,12 +333,20 @@ send(S0, ssh_msg_kexinit) ->
     {Msg, _Bytes, _C0} = ssh_transport:key_exchange_init_msg(S0#s.ssh),
     send(S0, Msg);
 
+send(S0, ssh_msg_ignore) ->
+    Msg = #ssh_msg_ignore{data = "unexpected_ignore_message"},
+    send(S0, Msg);
+
+send(S0, ssh_msg_unknown) ->
+    Msg = binary:encode_hex(<<"0000000C060900000000000000000000">>),
+    send(S0, Msg);
+
 send(S0=#s{alg_neg={undefined,PeerMsg}}, Msg=#ssh_msg_kexinit{}) ->
     S1 = opt(print_messages, S0,
 	     fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
     S2 = case PeerMsg of
 	     #ssh_msg_kexinit{} ->
-		 try ssh_transport:handle_kexinit_msg(PeerMsg, Msg, S1#s.ssh) of
+		 try ssh_transport:handle_kexinit_msg(PeerMsg, Msg, S1#s.ssh, init) of
 		     {ok,Cx} when ?role(S1) == server ->
 			 S1#s{alg = Cx#ssh.algorithms};
 		     {ok,_NextKexMsgBin,Cx} when ?role(S1) == client ->
@@ -360,7 +368,7 @@ send(S0=#s{alg_neg={undefined,PeerMsg}}, Msg=#ssh_msg_kexinit{}) ->
 send(S0, ssh_msg_kexdh_init) when ?role(S0) == client ->
     {OwnMsg, PeerMsg} = S0#s.alg_neg,
     {ok, NextKexMsgBin, C} = 
-	try ssh_transport:handle_kexinit_msg(PeerMsg, OwnMsg, S0#s.ssh)
+	try ssh_transport:handle_kexinit_msg(PeerMsg, OwnMsg, S0#s.ssh, init)
 	catch
 	    Class:Exc ->
 		fail("Algoritm negotiation failed!",
@@ -443,7 +451,7 @@ recv(S0 = #s{}) ->
 			    fail("2 kexint received!!", S);
 					
 			{OwnMsg, _} ->
-			    try ssh_transport:handle_kexinit_msg(PeerMsg, OwnMsg, S#s.ssh) of
+			    try ssh_transport:handle_kexinit_msg(PeerMsg, OwnMsg, S#s.ssh, init) of
 				{ok,C} when ?role(S) == server ->
 				    S#s{alg_neg = {OwnMsg, PeerMsg},
 					alg = C#ssh.algorithms,
@@ -796,23 +804,23 @@ report_trace(Class, Term, S) ->
 	  fun(true) -> {"~s ~p",[Class,Term]} end)
      ).
 
-seqnum_trace(S) ->
+seqnum_trace(S, S0) ->
     opt(print_seqnums, S,
-	fun(true) when S#s.ssh#ssh.send_sequence =/= S#s.ssh#ssh.send_sequence,
-		       S#s.ssh#ssh.recv_sequence =/= S#s.ssh#ssh.recv_sequence ->
+	fun(true) when S0#s.ssh#ssh.send_sequence =/= S#s.ssh#ssh.send_sequence,
+		       S0#s.ssh#ssh.recv_sequence =/= S#s.ssh#ssh.recv_sequence ->
 		{"~p seq num: send ~p->~p,  recv ~p->~p~n",
 		 [?role(S),
-		  S#s.ssh#ssh.send_sequence, S#s.ssh#ssh.send_sequence,
-		  S#s.ssh#ssh.recv_sequence, S#s.ssh#ssh.recv_sequence
+		  S0#s.ssh#ssh.send_sequence, S#s.ssh#ssh.send_sequence,
+		  S0#s.ssh#ssh.recv_sequence, S#s.ssh#ssh.recv_sequence
 		 ]};
-	   (true) when S#s.ssh#ssh.send_sequence =/=  S#s.ssh#ssh.send_sequence ->
+	   (true) when S0#s.ssh#ssh.send_sequence =/=  S#s.ssh#ssh.send_sequence ->
 		{"~p seq num: send ~p->~p~n",
 		 [?role(S),
-		  S#s.ssh#ssh.send_sequence, S#s.ssh#ssh.send_sequence]};
-	   (true) when S#s.ssh#ssh.recv_sequence =/=  S#s.ssh#ssh.recv_sequence ->
+		  S0#s.ssh#ssh.send_sequence, S#s.ssh#ssh.send_sequence]};
+	   (true) when S0#s.ssh#ssh.recv_sequence =/=  S#s.ssh#ssh.recv_sequence ->
 		{"~p seq num: recv ~p->~p~n",
 		 [?role(S),
-		  S#s.ssh#ssh.recv_sequence, S#s.ssh#ssh.recv_sequence]}
+		  S0#s.ssh#ssh.recv_sequence, S#s.ssh#ssh.recv_sequence]}
 	end).
 
 print_traces(S) when S#s.prints == [] -> S;
-- 
2.33.0

