1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
5%%
6%% The contents of this file are subject to the Erlang Public License,
7%% Version 1.1, (the "License"); you may not use this file except in
8%% compliance with the License. You should have received a copy of the
9%% Erlang Public License along with this software. If not, it can be
10%% retrieved online at http://www.erlang.org/.
11%%
12%% Software distributed under the License is distributed on an "AS IS"
13%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14%% the License for the specific language governing rights and limitations
15%% under the License.
16%%
17%% %CopyrightEnd%
18%%
19
20%%
21
22-module(ssh_protocol_SUITE).
23
24-include_lib("common_test/include/ct.hrl").
25-include_lib("kernel/include/inet.hrl").
26-include("ssh.hrl").		% ?UINT32, ?BYTE, #ssh{} ...
27-include("ssh_transport.hrl").
28-include("ssh_auth.hrl").
29-include("ssh_test_lib.hrl").
30
31-export([
32         suite/0,
33         all/0,
34         groups/0,
35         init_per_suite/1,
36         end_per_suite/1,
37         init_per_testcase/2,
38         end_per_testcase/2
39        ]).
40
41-export([
42         bad_long_service_name/1,
43         bad_packet_length/2,
44         bad_service_name/1,
45         bad_service_name/2,
46         bad_service_name_length/2,
47         bad_service_name_then_correct/1,
48         bad_very_long_service_name/1,
49         client_handles_keyboard_interactive_0_pwds/1,
50         client_info_line/1,
51         do_gex_client_init/3,
52         do_gex_client_init_old/3,
53         empty_service_name/1,
54         ext_info_c/1,
55         ext_info_s/1,
56         gex_client_init_option_groups/1,
57         gex_client_init_option_groups_file/1,
58         gex_client_init_option_groups_moduli_file/1,
59         gex_client_old_request_exact/1,
60         gex_client_old_request_noexact/1,
61         gex_server_gex_limit/1,
62         lib_match/1,
63         lib_no_match/1,
64         lib_works_as_client/1,
65         lib_works_as_server/1,
66         modify_append/1,
67         modify_combo/1,
68         modify_prepend/1,
69         modify_rm/1,
70         no_common_alg_client_disconnects/1,
71         no_common_alg_server_disconnects/1,
72         no_ext_info_s1/1,
73         no_ext_info_s2/1,
74         packet_length_too_large/1,
75         packet_length_too_short/1,
76         preferred_algorithms/1,
77         service_name_length_too_large/1,
78         service_name_length_too_short/1
79        ]).
80
81-define(NEWLINE, <<"\r\n">>).
82-define(REKEY_DATA_TMO, 65000).
83
84-define(DEFAULT_KEX, 'diffie-hellman-group14-sha256').
85-define(EXTRA_KEX, 'diffie-hellman-group1-sha1').
86
87-define(CIPHERS, ['aes256-ctr','aes192-ctr','aes128-ctr','aes128-cbc','3des-cbc']).
88-define(DEFAULT_CIPHERS, (fun() -> Ciphs = filter_supported(cipher, ?CIPHERS),
89                                   [{client2server,Ciphs}, {server2client,Ciphs}]
90                          end)()
91        ).
92
93
94-define(v(Key, Config), proplists:get_value(Key, Config)).
95-define(v(Key, Config, Default), proplists:get_value(Key, Config, Default)).
96
97
98%%--------------------------------------------------------------------
99%% Common Test interface functions -----------------------------------
100%%--------------------------------------------------------------------
101
102suite() ->
103    [{ct_hooks,[ts_install_cth]},
104     {timetrap,{seconds,40}}].
105
106all() ->
107    [{group,tool_tests},
108     client_info_line,
109     {group,kex},
110     {group,service_requests},
111     {group,authentication},
112     {group,packet_size_error},
113     {group,field_size_error},
114     {group,ext_info},
115     {group,preferred_algorithms}
116    ].
117
118groups() ->
119    [{tool_tests, [], [lib_works_as_client,
120		       lib_works_as_server,
121		       lib_match,
122		       lib_no_match
123		      ]},
124     {packet_size_error, [], [packet_length_too_large,
125			      packet_length_too_short]},
126
127     {field_size_error, [], [service_name_length_too_large,
128			     service_name_length_too_short]},
129
130     {kex, [], [no_common_alg_server_disconnects,
131		no_common_alg_client_disconnects,
132		gex_client_init_option_groups,
133		gex_server_gex_limit,
134		gex_client_init_option_groups_moduli_file,
135		gex_client_init_option_groups_file,
136		gex_client_old_request_exact,
137		gex_client_old_request_noexact
138		]},
139     {service_requests, [], [bad_service_name,
140			     bad_long_service_name,
141			     bad_very_long_service_name,
142			     empty_service_name,
143			     bad_service_name_then_correct
144			    ]},
145     {authentication, [], [client_handles_keyboard_interactive_0_pwds
146			  ]},
147     {ext_info, [], [no_ext_info_s1,
148                     no_ext_info_s2,
149                     ext_info_s,
150                     ext_info_c
151                    ]},
152     {preferred_algorithms, [], [preferred_algorithms,
153                                 modify_append,
154                                 modify_prepend,
155                                 modify_rm,
156                                 modify_combo
157                                ]}
158    ].
159
160
161init_per_suite(Config) ->
162    ?CHECK_CRYPTO(start_std_daemon( setup_dirs( start_apps(Config)))).
163
164end_per_suite(Config) ->
165    stop_apps(Config).
166
167
168
169init_per_testcase(no_common_alg_server_disconnects, Config) ->
170    start_std_daemon(Config, [{preferred_algorithms,[{public_key,['ssh-rsa']},
171                                                     {cipher,?DEFAULT_CIPHERS}
172                                                    ]}]);
173
174init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
175				   TC == gex_client_init_option_groups_moduli_file ;
176				   TC == gex_client_init_option_groups_file ;
177				   TC == gex_server_gex_limit ;
178				   TC == gex_client_old_request_exact ;
179				   TC == gex_client_old_request_noexact ->
180    Opts = case TC of
181	       gex_client_init_option_groups ->
182		   [{dh_gex_groups,
183                     [{1023, 5,
184                       16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A770E2EC9F
185                      }]}];
186	       gex_client_init_option_groups_file ->
187		   DataDir = proplists:get_value(data_dir, Config),
188		   F = filename:join(DataDir, "dh_group_test"),
189		   [{dh_gex_groups, {file,F}}];
190	       gex_client_init_option_groups_moduli_file ->
191		   DataDir = proplists:get_value(data_dir, Config),
192		   F = filename:join(DataDir, "dh_group_test.moduli"),
193		   [{dh_gex_groups, {ssh_moduli_file,F}}];
194	       _ when TC == gex_server_gex_limit ;
195		      TC == gex_client_old_request_exact ;
196		      TC == gex_client_old_request_noexact ->
197		    [{dh_gex_groups,
198                      [{1023, 2, 16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A771225323},
199                       {1535, 5, 16#D1391174233D315398FE2830AC6B2B66BCCD01B0A634899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2ABD1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073F0C21B8B54C3823DB2EF068927E5D747498F96E1E827},
200                       {3071, 2, 16#DFAA35D35531E0F524F0099877A482D2AC8D589F374394A262A8E81A8A4FB2F65FADBAB395E05D147B29D486DFAA41F41597A256DA82A8B6F76401AED53D0253F956CEC610D417E42E3B287F7938FC24D8821B40BFA218A956EB7401BED6C96C68C7FD64F8170A8A76B953DD2F05420118F6B144D8FE48060A2BCB85056B478EDEF96DBC70427053ECD2958C074169E9550DD877779A3CF17C5AC850598C7586BEEA9DCFE9DD2A5FB62DF5F33EA7BC00CDA31B9D2DD721F979EA85B6E63F0C4E30BDDCD3A335522F9004C4ED50B15DC537F55324DD4FA119FB3F101467C6D7E1699DE4B3E3C478A8679B8EB3FA5C9B826B44530FD3BE9AD3063B240B0C853EBDDBD68DD940332D98F148D5D9E1DC977D60A0D23D0CA1198637FEAE4E7FAAC173AF2B84313A666CFB4EE6972811921D0AD867CE57F3BBC8D6CB057E3B66757BB46C9F72662624D44E14528327E3A7100E81A12C43C4E236118318CD90C8AA185BBB0C764826DAEAEE8DD245C5B451B4944E6122CC522D1C335C2EEF9429825A2B}
201                      ]},
202                     {dh_gex_limits, {1023,2000}}
203		    ];
204	       _ ->
205		   []
206	   end,
207    start_std_daemon(Config,
208		     [{preferred_algorithms,[{cipher,?DEFAULT_CIPHERS}
209                                            ]}
210		      | Opts]);
211init_per_testcase(_TestCase, Config) ->
212    check_std_daemon_works(Config, ?LINE).
213
214end_per_testcase(no_common_alg_server_disconnects, Config) ->
215    stop_std_daemon(Config);
216end_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
217				  TC == gex_client_init_option_groups_moduli_file ;
218				  TC == gex_client_init_option_groups_file ;
219				  TC == gex_server_gex_limit ;
220				  TC == gex_client_old_request_exact ;
221				  TC == gex_client_old_request_noexact ->
222    stop_std_daemon(Config);
223end_per_testcase(_TestCase, Config) ->
224    check_std_daemon_works(Config, ?LINE).
225
226%%%--------------------------------------------------------------------
227%%% Test Cases --------------------------------------------------------
228%%%--------------------------------------------------------------------
229
230%%%--------------------------------------------------------------------
231%%% Connect to an erlang server and check that the testlib acts as a client.
232lib_works_as_client(Config) ->
233    %% Connect and negotiate keys
234    {ok,InitialState} = ssh_trpt_test_lib:exec(
235			  [{set_options, [print_ops, print_seqnums, print_messages]}]
236			 ),
237    {ok,AfterKexState} = connect_and_kex(Config, InitialState),
238
239    %% Do the authentcation
240    {User,Pwd} = server_user_password(Config),
241    {ok,EndState} =
242	ssh_trpt_test_lib:exec(
243	  [{send, #ssh_msg_service_request{name = "ssh-userauth"}},
244	   {match, #ssh_msg_service_accept{name = "ssh-userauth"}, receive_msg},
245	   {send, #ssh_msg_userauth_request{user = User,
246					    service = "ssh-connection",
247					    method = "password",
248					    data = <<?BOOLEAN(?FALSE),
249						     ?STRING(unicode:characters_to_binary(Pwd))>>
250					   }},
251	   {match, #ssh_msg_userauth_success{_='_'}, receive_msg}
252	  ], AfterKexState),
253
254    %% Disconnect
255    {ok,_} =
256	ssh_trpt_test_lib:exec(
257	  [{send, #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
258				      description = "End of the fun",
259				      language = ""
260				     }},
261	   close_socket
262	  ], EndState).
263
264
265%%--------------------------------------------------------------------
266%%% Connect an erlang client and check that the testlib can act as a server.
267lib_works_as_server(Config) ->
268    {User,_Pwd} = server_user_password(Config),
269
270    %% Create a listening socket as server socket:
271    {ok,InitialState} = ssh_trpt_test_lib:exec(listen),
272    HostPort = ssh_trpt_test_lib:server_host_port(InitialState),
273
274    %% Start a process handling one connection on the server side:
275    spawn_link(
276      fun() ->
277	      {ok,_} =
278		  ssh_trpt_test_lib:exec(
279		    [{set_options, [print_ops, print_messages]},
280		     {accept, [{system_dir, system_dir(Config)},
281			       {user_dir, user_dir(Config)}]},
282		     receive_hello,
283		     {send, hello},
284
285		     {send, ssh_msg_kexinit},
286		     {match, #ssh_msg_kexinit{_='_'}, receive_msg},
287
288		     {match, #ssh_msg_kexdh_init{_='_'}, receive_msg},
289		     {send, ssh_msg_kexdh_reply},
290
291		     {send, #ssh_msg_newkeys{}},
292		     {match,  #ssh_msg_newkeys{_='_'}, receive_msg},
293
294		     {match, #ssh_msg_service_request{name="ssh-userauth"}, receive_msg},
295		     {send, #ssh_msg_service_accept{name="ssh-userauth"}},
296
297		     {match, #ssh_msg_userauth_request{service="ssh-connection",
298						       method="none",
299						       user=User,
300						       _='_'}, receive_msg},
301
302		     {send, #ssh_msg_userauth_failure{authentications = "password",
303						      partial_success = false}},
304
305		     {match, #ssh_msg_userauth_request{service="ssh-connection",
306						       method="password",
307						       user=User,
308						       _='_'}, receive_msg},
309		     {send, #ssh_msg_userauth_success{}},
310		     close_socket,
311		     print_state
312		    ],
313		    InitialState)
314      end),
315
316    %% and finally connect to it with a regular Erlang SSH client:
317    {ok,_} = std_connect(HostPort, Config,
318			 [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
319                                                 {cipher,?DEFAULT_CIPHERS}
320                                                ]}
321                         ]
322			).
323
324%%--------------------------------------------------------------------
325%%% Matching
326lib_match(_Config) ->
327    {ok,_} =
328	ssh_trpt_test_lib:exec([{set_options, [print_ops]},
329				{match, abc, abc},
330				{match, '$a', {cde,fgh}},
331				{match, {cde,fgh}, '$a'},
332				{match, '_', {cde,fgh}},
333				{match, [a,'$a',b], [a,{cde,fgh},b]},
334				{match, [a,'$a'|'$b'], [a,{cde,fgh},b,c]},
335				{match, '$b', [b,c]}
336			       ]).
337
338%%--------------------------------------------------------------------
339%%% Not matching
340lib_no_match(_Config) ->
341    case ssh_trpt_test_lib:exec([{set_options, [print_ops]},
342				 {match, '$x', b},
343				 {match, a, '$x'}])
344    of
345	{ok,_} -> {fail,"Unexpected match"};
346	{error, {_Op,{expected,a,b},_State}} -> ok
347    end.
348
349%%--------------------------------------------------------------------
350%%% Algo negotiation fail.  This should result in a ssh_msg_disconnect
351%%% being sent from the server.
352no_common_alg_server_disconnects(Config) ->
353    {ok,_} =
354	ssh_trpt_test_lib:exec(
355	  [{set_options, [print_ops, {print_messages,detail}]},
356	   {connect,
357	    server_host(Config),server_port(Config),
358	    [{silently_accept_hosts, true},
359	     {user_dir, user_dir(Config)},
360	     {user_interaction, false},
361	     {preferred_algorithms,[{public_key,['ssh-dss']},
362                                    {cipher,?DEFAULT_CIPHERS}
363                                   ]}
364	    ]},
365	   receive_hello,
366	   {send, hello},
367	   {match, #ssh_msg_kexinit{_='_'}, receive_msg},
368	   {send, ssh_msg_kexinit},  % with server unsupported 'ssh-dss' !
369	   {match, disconnect(), receive_msg}
370	  ]
371	 ).
372
373%%--------------------------------------------------------------------
374%%% Algo negotiation fail.  This should result in a ssh_msg_disconnect
375%%% being sent from the client.
376no_common_alg_client_disconnects(Config) ->
377    %% Create a listening socket as server socket:
378    {ok,InitialState} = ssh_trpt_test_lib:exec(listen),
379    HostPort = ssh_trpt_test_lib:server_host_port(InitialState),
380    Parent = self(),
381
382    %% Start a process handling one connection on the server side:
383    Pid =
384	spawn_link(
385	  fun() ->
386		  Parent !
387		      {result,self(),
388		       ssh_trpt_test_lib:exec(
389			 [{set_options, [print_ops, {print_messages,detail}]},
390			  {accept, [{system_dir, system_dir(Config)},
391				    {user_dir, user_dir(Config)}]},
392			  receive_hello,
393			  {send, hello},
394			  {match, #ssh_msg_kexinit{_='_'}, receive_msg},
395			  {send,  #ssh_msg_kexinit{ % with unsupported "SOME-UNSUPPORTED"
396				     cookie = <<80,158,95,51,174,35,73,130,246,141,200,49,180,190,82,234>>,
397				     kex_algorithms = [atom_to_list(?DEFAULT_KEX)],
398				     server_host_key_algorithms = ["SOME-UNSUPPORTED"],  % SIC!
399				     encryption_algorithms_client_to_server = ["aes128-ctr"],
400				     encryption_algorithms_server_to_client = ["aes128-ctr"],
401				     mac_algorithms_client_to_server = ["hmac-sha2-256"],
402				     mac_algorithms_server_to_client = ["hmac-sha2-256"],
403				     compression_algorithms_client_to_server = ["none"],
404				     compression_algorithms_server_to_client = ["none"],
405				     languages_client_to_server = [],
406				     languages_server_to_client = [],
407				     first_kex_packet_follows = false,
408				     reserved = 0
409				    }},
410			  {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}
411			 ],
412			 InitialState)
413		      }
414	  end),
415
416    %% and finally connect to it with a regular Erlang SSH client
417    %% which of course does not support SOME-UNSUPPORTED as pub key algo:
418    Result = std_connect(HostPort, Config, [{preferred_algorithms,[{public_key,['ssh-dss']},
419                                                                   {cipher,?DEFAULT_CIPHERS}
420                                                                  ]}]),
421    ct:log("Result of connect is ~p",[Result]),
422
423    receive
424	{result,Pid,{ok,_}} ->
425	    ok;
426	{result,Pid,{error,{Op,ExecResult,S}}} ->
427	    ct:log("ERROR!~nOp = ~p~nExecResult = ~p~nState =~n~s",
428		   [Op,ExecResult,ssh_trpt_test_lib:format_msg(S)]),
429	    {fail, ExecResult};
430	X ->
431	    ct:log("¤¤¤¤¤"),
432	    ct:fail(X)
433    after
434	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
435    end.
436
437%%%--------------------------------------------------------------------
438gex_client_init_option_groups(Config) ->
439    do_gex_client_init(Config, {512, 2048, 4000},
440		       {5,16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A770E2EC9F}
441                      ).
442
443gex_client_init_option_groups_file(Config) ->
444    do_gex_client_init(Config, {2000, 2048, 4000},
445                       {5, 16#DFAA35D35531E0F524F0099877A482D2AC8D589F374394A262A8E81A8A4FB2F65FADBAB395E05D147B29D486DFAA41F41597A256DA82A8B6F76401AED53D0253F956CEC610D417E42E3B287F7938FC24D8821B40BFA218A956EB7401BED6C96C68C7FD64F8170A8A76B953DD2F05420118F6B144D8FE48060A2BCB85056B478EDEF96DBC70427053ECD2958C074169E9550DD877779A3CF17C5AC850598C7586BEEA9DCFE9DD2A5FB62DF5F33EA7BC00CDA31B9D2DD721F979EA85B6E63F0C4E30BDDCD3A335522F9004C4ED50B15DC537F55324DD4FA119FB3F101467C6D7E1699DE4B3E3C478A8679B8EB3FA5C9B826B44530FD3BE9AD3063B240B0C853EBDDBD68DD940332D98F148D5D9E1DC977D60A0D23D0CA1198637FEAE4E7FAAC173AF2B84313A666CFB4EE6972811921D0AD867CE57F3BBC8D6CB057E3B66757BB46C9F72662624D44E14528327E3A7100E81A12C43C4E236118318CD90C8AA185BBB0C764826DAEAEE8DD245C5B451B4944E6122CC522D1C335C2EEF9424273F1F}
446                      ).
447
448gex_client_init_option_groups_moduli_file(Config) ->
449    do_gex_client_init(Config, {2000, 2048, 4000},
450                       {5, 16#DD2047CBDBB6F8E919BC63DE885B34D0FD6E3DB2887D8B46FE249886ACED6B46DFCD5553168185FD376122171CD8927E60120FA8D01F01D03E58281FEA9A1ABE97631C828E41815F34FDCDF787419FE13A3137649AA93D2584230DF5F24B5C00C88B7D7DE4367693428C730376F218A53E853B0851BAB7C53C15DA7839CBE1285DB63F6FA45C1BB59FE1C5BB918F0F8459D7EF60ACFF5C0FA0F3FCAD1C5F4CE4416D4F4B36B05CDCEBE4FB879E95847EFBC6449CD190248843BC7EDB145FBFC4EDBB1A3C959298F08F3BA2CFBE231BBE204BE6F906209D28BD4820AB3E7BE96C26AE8A809ADD8D1A5A0B008E9570FA4C4697E116B8119892C604293683A9635F}
451                       ).
452
453gex_server_gex_limit(Config) ->
454    do_gex_client_init(Config, {1000, 3000, 4000},
455		       %% {7,91}).
456                       {5, 16#D1391174233D315398FE2830AC6B2B66BCCD01B0A634899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2ABD1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073F0C21B8B54C3823DB2EF068927E5D747498F96E1E827}
457                       ).
458
459
460do_gex_client_init(Config, {Min,N,Max}, {G,P}) ->
461    {ok,_} =
462	ssh_trpt_test_lib:exec(
463	  [{set_options, [print_ops, print_seqnums, print_messages]},
464	   {connect,
465	    server_host(Config),server_port(Config),
466	    [{silently_accept_hosts, true},
467	     {user_dir, user_dir(Config)},
468	     {user_interaction, false},
469	     {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha256']},
470                                    {cipher,?DEFAULT_CIPHERS}
471                                   ]}
472	    ]},
473	   receive_hello,
474	   {send, hello},
475	   {send, ssh_msg_kexinit},
476	   {match, #ssh_msg_kexinit{_='_'}, receive_msg},
477	   {send, #ssh_msg_kex_dh_gex_request{min = Min,
478					      n = N,
479					      max = Max}},
480	   {match, #ssh_msg_kex_dh_gex_group{p=P, g=G, _='_'},  receive_msg}
481	  ]
482	 ).
483
484%%%--------------------------------------------------------------------
485gex_client_old_request_exact(Config)  ->
486    do_gex_client_init_old(Config, 1023,
487                           {2, 16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A771225323}
488                           ).
489
490gex_client_old_request_noexact(Config) ->
491    do_gex_client_init_old(Config, 1400,
492                           {5, 16#D1391174233D315398FE2830AC6B2B66BCCD01B0A634899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2ABD1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073F0C21B8B54C3823DB2EF068927E5D747498F96E1E827}
493                           ).
494
495do_gex_client_init_old(Config, N, {G,P}) ->
496    {ok,_} =
497	ssh_trpt_test_lib:exec(
498	  [{set_options, [print_ops, print_seqnums, print_messages]},
499	   {connect,
500	    server_host(Config),server_port(Config),
501	    [{silently_accept_hosts, true},
502	     {user_dir, user_dir(Config)},
503	     {user_interaction, false},
504	     {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha256']},
505                                    {cipher,?DEFAULT_CIPHERS}
506                                   ]}
507	    ]},
508	   receive_hello,
509	   {send, hello},
510	   {send, ssh_msg_kexinit},
511	   {match, #ssh_msg_kexinit{_='_'}, receive_msg},
512	   {send, #ssh_msg_kex_dh_gex_request_old{n = N}},
513	   {match, #ssh_msg_kex_dh_gex_group{p=P, g=G, _='_'},  receive_msg}
514	  ]
515	 ).
516
517%%%--------------------------------------------------------------------
518bad_service_name(Config) ->
519    bad_service_name(Config, "kfglkjf").
520
521bad_long_service_name(Config) ->
522    bad_service_name(Config,
523		     lists:duplicate(?SSH_MAX_PACKET_SIZE div 2, $a)).
524
525bad_very_long_service_name(Config) ->
526    bad_service_name(Config,
527		     lists:duplicate(?SSH_MAX_PACKET_SIZE+5, $a)).
528
529empty_service_name(Config) ->
530    bad_service_name(Config, "").
531
532bad_service_name_then_correct(Config) ->
533    {ok,InitialState} = connect_and_kex(Config),
534    {ok,_} =
535	ssh_trpt_test_lib:exec(
536	  [{set_options, [print_ops, print_seqnums, print_messages]},
537	   {send, #ssh_msg_service_request{name = "kdjglkfdjgkldfjglkdfjglkfdjglkj"}},
538	   {send, #ssh_msg_service_request{name = "ssh-connection"}},
539	   {match, disconnect(), receive_msg}
540	   ], InitialState).
541
542
543bad_service_name(Config, Name) ->
544    {ok,InitialState} = connect_and_kex(Config),
545    {ok,_} =
546	ssh_trpt_test_lib:exec(
547	  [{set_options, [print_ops, print_seqnums, print_messages]},
548	   {send, #ssh_msg_service_request{name = Name}},
549	   {match, disconnect(), receive_msg}
550	  ], InitialState).
551
552%%%--------------------------------------------------------------------
553packet_length_too_large(Config) -> bad_packet_length(Config, +4).
554
555packet_length_too_short(Config) -> bad_packet_length(Config, -4).
556
557bad_packet_length(Config, LengthExcess) ->
558    PacketFun =
559	fun(Msg, Ssh) ->
560		BinMsg = ssh_message:encode(Msg),
561		ssh_transport:pack(BinMsg, Ssh, LengthExcess)
562	end,
563    {ok,InitialState} = connect_and_kex(Config),
564    {ok,_} =
565	ssh_trpt_test_lib:exec(
566	  [{set_options, [print_ops, print_seqnums, print_messages]},
567	   {send, {special,
568		   #ssh_msg_service_request{name="ssh-userauth"},
569		   PacketFun}},
570	   %% Prohibit remote decoder starvation:
571	   {send, #ssh_msg_service_request{name="ssh-userauth"}},
572	   {match, disconnect(), receive_msg}
573	  ], InitialState).
574
575%%%--------------------------------------------------------------------
576service_name_length_too_large(Config) -> bad_service_name_length(Config, +4).
577
578service_name_length_too_short(Config) -> bad_service_name_length(Config, -4).
579
580
581bad_service_name_length(Config, LengthExcess) ->
582    PacketFun =
583	fun(#ssh_msg_service_request{name=Service}, Ssh) ->
584		BinName = list_to_binary(Service),
585		BinMsg =
586		    <<?BYTE(?SSH_MSG_SERVICE_REQUEST),
587		      %% A bad string encoding of Service:
588		      ?UINT32(size(BinName)+LengthExcess), BinName/binary
589		    >>,
590		ssh_transport:pack(BinMsg, Ssh)
591	end,
592    {ok,InitialState} = connect_and_kex(Config),
593    {ok,_} =
594	ssh_trpt_test_lib:exec(
595	  [{set_options, [print_ops, print_seqnums, print_messages]},
596	   {send, {special,
597		   #ssh_msg_service_request{name="ssh-userauth"},
598		   PacketFun} },
599	   %% Prohibit remote decoder starvation:
600	   {send, #ssh_msg_service_request{name="ssh-userauth"}},
601	   {match, disconnect(), receive_msg}
602	  ], InitialState).
603
604%%%--------------------------------------------------------------------
605%%% This is due to a fault report (OTP-13255) with OpenSSH-6.6.1
606client_handles_keyboard_interactive_0_pwds(Config) ->
607    {User,_Pwd} = server_user_password(Config),
608
609    %% Create a listening socket as server socket:
610    {ok,InitialState} = ssh_trpt_test_lib:exec(listen),
611    HostPort = ssh_trpt_test_lib:server_host_port(InitialState),
612
613    %% Start a process handling one connection on the server side:
614    spawn_link(
615      fun() ->
616	      {ok,_} =
617		  ssh_trpt_test_lib:exec(
618		    [{set_options, [print_ops, print_messages]},
619		     {accept, [{system_dir, system_dir(Config)},
620			       {user_dir, user_dir(Config)}]},
621		     receive_hello,
622		     {send, hello},
623
624		     {send, ssh_msg_kexinit},
625		     {match, #ssh_msg_kexinit{_='_'}, receive_msg},
626
627		     {match, #ssh_msg_kexdh_init{_='_'}, receive_msg},
628		     {send, ssh_msg_kexdh_reply},
629
630		     {send, #ssh_msg_newkeys{}},
631		     {match,  #ssh_msg_newkeys{_='_'}, receive_msg},
632
633		     {match, #ssh_msg_service_request{name="ssh-userauth"}, receive_msg},
634		     {send, #ssh_msg_service_accept{name="ssh-userauth"}},
635
636		     {match, #ssh_msg_userauth_request{service="ssh-connection",
637						       method="none",
638						       user=User,
639						       _='_'}, receive_msg},
640		     {send, #ssh_msg_userauth_failure{authentications = "keyboard-interactive",
641						      partial_success = false}},
642
643		     {match, #ssh_msg_userauth_request{service="ssh-connection",
644						       method="keyboard-interactive",
645						       user=User,
646						       _='_'}, receive_msg},
647		     {send, #ssh_msg_userauth_info_request{name = "",
648							   instruction = "",
649							   language_tag = "",
650							   num_prompts = 1,
651							   data = <<0,0,0,10,80,97,115,115,119,111,114,100,58,32,0>>
652							  }},
653		     {match, #ssh_msg_userauth_info_response{num_responses = 1,
654							     _='_'}, receive_msg},
655
656		     %% the next is strange, but openssh 6.6.1 does this and this is what this testcase is about
657		     {send, #ssh_msg_userauth_info_request{name = "",
658							   instruction = "",
659							   language_tag = "",
660							   num_prompts = 0,
661							   data = <<>>
662							  }},
663		     {match, #ssh_msg_userauth_info_response{num_responses = 0,
664							     data = <<>>,
665							     _='_'}, receive_msg},
666		     %% Here we know that the tested fault is fixed
667		     {send, #ssh_msg_userauth_success{}},
668		     close_socket,
669		     print_state
670		    ],
671		    InitialState)
672      end),
673
674    %% and finally connect to it with a regular Erlang SSH client:
675    {ok,_} = std_connect(HostPort, Config,
676			 [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
677                                                 {cipher,?DEFAULT_CIPHERS}
678                                                ]}]
679			).
680
681
682
683%%%--------------------------------------------------------------------
684client_info_line(Config) ->
685    %% A client must not send an info-line. If it does, the server should handle
686    %% handle this gracefully
687    {ok,Pid} = ssh_eqc_event_handler:add_report_handler(),
688    DataDir = proplists:get_value(data_dir, Config),
689    {_, _, Port} = ssh_test_lib:daemon([{system_dir,DataDir}]),
690
691    %% Fake client:
692    {ok,S} = gen_tcp:connect("localhost",Port,[]),
693    gen_tcp:send(S,"An illegal info-string\r\n"),
694    gen_tcp:close(S),
695
696    %% wait for server to react:
697    timer:sleep(1000),
698
699    %% check if a badmatch was received:
700    {ok, Reports} = ssh_eqc_event_handler:get_reports(Pid),
701    case lists:any(fun({error_report,_,{_,supervisor_report,L}}) when is_list(L) ->
702			   lists:member({reason,{badmatch,{error,closed}}}, L);
703		      (_) ->
704			   false
705		   end, Reports) of
706	true ->
707	    ct:fail("Bad error report on info_line from client");
708	false ->
709	    ok
710    end.
711
712%%%--------------------------------------------------------------------
713%%% The server does not send the extension because
714%%% the client does not tell the server to send it
715no_ext_info_s1(Config) ->
716    %% Start the dameon
717    Server = {Pid,_,_} = ssh_test_lib:daemon([{send_ext_info,true},
718                                              {system_dir, system_dir(Config)}]),
719    {ok,AfterKexState} = connect_and_kex([{server,Server}|Config]),
720    {ok,_} =
721        ssh_trpt_test_lib:exec(
722          [{send, #ssh_msg_service_request{name = "ssh-userauth"}},
723	   {match, #ssh_msg_service_accept{name = "ssh-userauth"}, receive_msg}
724          ], AfterKexState),
725    ssh:stop_daemon(Pid).
726
727%%%--------------------------------------------------------------------
728%%% The server does not send the extension because
729%%% the server is not configured to send it
730no_ext_info_s2(Config) ->
731    %% Start the dameon
732    Server = {Pid,_,_} = ssh_test_lib:daemon([{send_ext_info,false},
733                                              {system_dir, system_dir(Config)}]),
734    {ok,AfterKexState} = connect_and_kex([{extra_options,[{recv_ext_info,true}]},
735                                          {server,Server}
736                                          | Config]),
737    {ok,_} =
738        ssh_trpt_test_lib:exec(
739          [{send, #ssh_msg_service_request{name = "ssh-userauth"}},
740	   {match, #ssh_msg_service_accept{name = "ssh-userauth"}, receive_msg}
741          ], AfterKexState),
742    ssh:stop_daemon(Pid).
743
744%%%--------------------------------------------------------------------
745%%% The server sends the extension
746ext_info_s(Config) ->
747    %% Start the dameon
748    Server = {Pid,_,_} = ssh_test_lib:daemon([{send_ext_info,true},
749                                              {system_dir, system_dir(Config)}]),
750    {ok,AfterKexState} = connect_and_kex([{extra_options,[{recv_ext_info,true}]},
751                                          {server,Server}
752                                          | Config]),
753    {ok,_} =
754        ssh_trpt_test_lib:exec(
755          [{match, #ssh_msg_ext_info{_='_'}, receive_msg}
756          ],
757          AfterKexState),
758    ssh:stop_daemon(Pid).
759
760%%%--------------------------------------------------------------------
761%%% The client sends the extension
762ext_info_c(Config) ->
763    %% Create a listening socket as server socket:
764    {ok,InitialState} = ssh_trpt_test_lib:exec(listen),
765    HostPort = ssh_trpt_test_lib:server_host_port(InitialState),
766
767    Parent = self(),
768    %% Start a process handling one connection on the server side:
769    Pid =
770        spawn_link(
771          fun() ->
772                  Result =
773                      ssh_trpt_test_lib:exec(
774                        [{set_options, [print_ops, print_messages]},
775                         {accept, [{system_dir, system_dir(Config)},
776                                   {user_dir, user_dir(Config)},
777                                   {recv_ext_info, true}
778                                  ]},
779                         receive_hello,
780                         {send, hello},
781
782                         {send, ssh_msg_kexinit},
783                         {match, #ssh_msg_kexinit{_='_'}, receive_msg},
784
785                         {match, #ssh_msg_kexdh_init{_='_'}, receive_msg},
786                         {send, ssh_msg_kexdh_reply},
787
788                         {send, #ssh_msg_newkeys{}},
789                         {match,  #ssh_msg_newkeys{_='_'}, receive_msg},
790
791                         {match, #ssh_msg_ext_info{_='_'}, receive_msg},
792
793                         close_socket,
794                         print_state
795                        ],
796                        InitialState),
797                  Parent ! {result,self(),Result}
798          end),
799
800    %% connect to it with a regular Erlang SSH client
801    %% (expect error due to the close_socket in daemon):
802    {error,_} = std_connect(HostPort, Config,
803                            [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
804                                                    {cipher,?DEFAULT_CIPHERS}
805                                                   ]},
806                             {tstflg, [{ext_info_client,true}]},
807                             {send_ext_info, true}
808                            ]
809                           ),
810
811    %% Check that the daemon got expected result:
812    receive
813        {result, Pid, {ok,_}} -> ok;
814        {result, Pid, Error} -> ct:fail("Error: ~p",[Error])
815    end.
816
817
818%%%----------------------------------------------------------------
819%%%
820preferred_algorithms(Config) ->
821    Ciphers = filter_supported(cipher, ?CIPHERS),
822    {error,{eoptions,{{preferred_algorithms,{kex,[some_unknown_algo]}},
823                      "Unsupported value(s) found"}}} =
824        chk_pref_algs(Config,
825                      [?DEFAULT_KEX],
826                      Ciphers,
827                      [{preferred_algorithms, [{kex,[some_unknown_algo,?DEFAULT_KEX]},
828                                               {cipher,Ciphers}
829                                              ]}
830                      ]).
831
832%%%----------------------------------------------------------------
833%%%
834modify_append(Config) ->
835    Ciphers = filter_supported(cipher, ?CIPHERS),
836    {ok,_} =
837        chk_pref_algs(Config,
838                      [?DEFAULT_KEX, ?EXTRA_KEX],
839                      Ciphers,
840                      [{preferred_algorithms, [{kex,[?DEFAULT_KEX]},
841                                               {cipher,Ciphers}
842                                              ]},
843                       {modify_algorithms, [{append,[{kex,[some_unknown_algo,?EXTRA_KEX]}]}]}
844                      ]).
845
846%%%----------------------------------------------------------------
847%%%
848modify_prepend(Config) ->
849    Ciphers = filter_supported(cipher, ?CIPHERS),
850    {ok,_} =
851        chk_pref_algs(Config,
852                      [?EXTRA_KEX, ?DEFAULT_KEX],
853                      Ciphers,
854                      [{preferred_algorithms, [{kex,[?DEFAULT_KEX]},
855                                               {cipher,Ciphers}
856                                              ]},
857                       {modify_algorithms, [{prepend,[{kex,[some_unknown_algo,?EXTRA_KEX]}]}]}
858                      ]).
859
860%%%----------------------------------------------------------------
861%%%
862modify_rm(Config) ->
863    Ciphers = filter_supported(cipher, ?CIPHERS),
864    {ok,_} =
865        chk_pref_algs(Config,
866                      [?DEFAULT_KEX],
867                      tl(Ciphers),
868                      [{preferred_algorithms, [{kex,[?DEFAULT_KEX,?EXTRA_KEX]},
869                                               {cipher,Ciphers}
870                                              ]},
871                       {modify_algorithms, [{rm,[{kex,[some_unknown_algo,?EXTRA_KEX]},
872                                                 {cipher,[hd(Ciphers)]}
873                                                ]}
874                                           ]}
875                      ]).
876
877
878%%%----------------------------------------------------------------
879%%%
880modify_combo(Config) ->
881    Ciphers = filter_supported(cipher, ?CIPHERS),
882    LastC = lists:last(Ciphers),
883    {ok,_} =
884        chk_pref_algs(Config,
885                      [?DEFAULT_KEX],
886                      [LastC] ++ (tl(Ciphers)--[LastC]) ++ [hd(Ciphers)],
887                      [{preferred_algorithms, [{kex,[?DEFAULT_KEX,?EXTRA_KEX]},
888                                               {cipher,Ciphers}
889                                              ]},
890                       {modify_algorithms, [{rm,[{kex,[some_unknown_algo,?EXTRA_KEX]}
891                                                ]},
892                                            {prepend,[{cipher,[{server2client,[LastC]}]}
893                                                     ]},
894                                            {append,[{cipher,[a,hd(Ciphers),b]}
895                                                    ]}
896                                           ]}
897                      ]).
898
899%%%================================================================
900%%%==== Internal functions ========================================
901%%%================================================================
902
903chk_pref_algs(Config,
904              ExpectedKex,
905              ExpectedCiphers,
906              ServerPrefOpts) ->
907    %% Start the dameon
908    case ssh_test_lib:daemon(
909                      [{send_ext_info,false},
910                       {recv_ext_info,false},
911                       {system_dir, system_dir(Config)}
912                       | ServerPrefOpts])
913    of
914        {_,Host,Port} ->
915            %% Check the Kex part
916            ssh_trpt_test_lib:exec(
917              [{set_options, [print_ops, {print_messages,detail}]},
918               {connect, Host, Port,
919                [{silently_accept_hosts, true},
920                 {user_dir, user_dir(Config)},
921                 {user_interaction, false}
922                ]},
923               {send, hello},
924               receive_hello,
925               {match,
926                #ssh_msg_kexinit{
927                   kex_algorithms = to_lists(ExpectedKex),
928                   encryption_algorithms_server_to_client = to_lists(ExpectedCiphers),
929                   _   = '_'},
930                receive_msg}
931              ]);
932        Error ->
933            Error
934    end.
935
936
937filter_supported(K, Algs) -> Algs -- (Algs--supported(K)).
938
939supported(_K) -> proplists:get_value(
940                   server2client,
941                   ssh_transport:supported_algorithms(cipher)).
942
943to_lists(L) -> lists:map(fun erlang:atom_to_list/1, L).
944
945
946%%%---- init_suite and end_suite ---------------------------------------
947start_apps(Config) ->
948    catch ssh:stop(),
949    ok = ssh:start(),
950    Config.
951
952stop_apps(_Config) ->
953    ssh:stop().
954
955
956setup_dirs(Config) ->
957    ct:log("Pub keys setup for: ~p",
958           [ssh_test_lib:setup_all_user_host_keys(Config)]),
959    Config.
960
961system_dir(Config) -> filename:join(proplists:get_value(priv_dir, Config), system).
962
963user_dir(Config) -> proplists:get_value(priv_dir, Config).
964
965%%%----------------------------------------------------------------
966start_std_daemon(Config) ->
967    start_std_daemon(Config, []).
968
969start_std_daemon(Config, ExtraOpts) ->
970    PrivDir = proplists:get_value(priv_dir, Config),
971    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
972    file:make_dir(UserDir),
973    UserPasswords = [{"user1","pwd1"}],
974    Options = [%%{preferred_algorithms,[{public_key,['ssh-rsa']}]}, %% For some test cases
975	       {system_dir, system_dir(Config)},
976	       {user_dir, UserDir},
977	       {user_passwords, UserPasswords},
978	       {failfun, fun ssh_test_lib:failfun/2}
979	       | ExtraOpts],
980    Ref = {Server, Host, Port} = ssh_test_lib:daemon(Options),
981    ct:log("Std server ~p started at ~p:~p~nOptions=~p",[Server, Host, Port, Options]),
982    [{server,Ref}, {user_passwords, UserPasswords} | Config].
983
984
985stop_std_daemon(Config) ->
986    ssh:stop_daemon(server_pid(Config)),
987    ct:log("Std server ~p at ~p:~p stopped", [server_pid(Config), server_host(Config), server_port(Config)]),
988    lists:keydelete(server, 1, Config).
989
990
991check_std_daemon_works(Config, Line) ->
992    case std_connect(Config) of
993	{ok,C} ->
994	    ct:log("Server ~p:~p ~p is ok at line ~p",
995		   [server_host(Config), server_port(Config),
996		    server_pid(Config), Line]),
997	    ok = ssh:close(C),
998	    Config;
999	Error = {error,_} ->
1000	    ct:fail("Standard server ~p:~p ~p is ill at line ~p: ~p",
1001		    [server_host(Config), server_port(Config),
1002		     server_pid(Config), Line, Error])
1003    end.
1004
1005server_pid(Config)  -> element(1,?v(server,Config)).
1006server_host(Config) -> element(2,?v(server,Config)).
1007server_port(Config) -> element(3,?v(server,Config)).
1008
1009server_user_password(Config) -> server_user_password(1, Config).
1010
1011server_user_password(N, Config) -> lists:nth(N, ?v(user_passwords,Config)).
1012
1013
1014std_connect(Config) ->
1015    std_connect({server_host(Config), server_port(Config)}, Config).
1016
1017std_connect({Host,Port}, Config) ->
1018    std_connect({Host,Port}, Config, []).
1019
1020std_connect({Host,Port}, Config, Opts) ->
1021    std_connect(Host, Port, Config, Opts).
1022
1023std_connect(Host, Port, Config, Opts) ->
1024    {User,Pwd} = server_user_password(Config),
1025    ssh:connect(Host, Port,
1026		%% Prefere User's Opts to the default opts
1027		[O || O = {Tag,_} <- [{user,User},{password,Pwd},
1028				      {silently_accept_hosts, true},
1029                                      {save_accepted_host, false},
1030				      {user_dir, user_dir(Config)},
1031				      {user_interaction, false}],
1032		      not lists:keymember(Tag, 1, Opts)
1033		] ++ Opts,
1034		30000).
1035
1036%%%----------------------------------------------------------------
1037connect_and_kex(Config) ->
1038    connect_and_kex(Config, ssh_trpt_test_lib:exec([]) ).
1039
1040connect_and_kex(Config, InitialState) ->
1041    ssh_trpt_test_lib:exec(
1042      [{connect,
1043	server_host(Config),server_port(Config),
1044	[{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
1045                                {cipher,?DEFAULT_CIPHERS}
1046                               ]},
1047         {silently_accept_hosts, true},
1048         {recv_ext_info, false},
1049	 {user_dir, user_dir(Config)},
1050	 {user_interaction, false}
1051         | proplists:get_value(extra_options,Config,[])
1052        ]},
1053       receive_hello,
1054       {send, hello},
1055       {send, ssh_msg_kexinit},
1056       {match, #ssh_msg_kexinit{_='_'}, receive_msg},
1057       {send, ssh_msg_kexdh_init},
1058       {match,# ssh_msg_kexdh_reply{_='_'}, receive_msg},
1059       {send, #ssh_msg_newkeys{}},
1060       {match, #ssh_msg_newkeys{_='_'}, receive_msg}
1061      ],
1062      InitialState).
1063
1064%%%----------------------------------------------------------------
1065
1066%%% For matching peer disconnection
1067disconnect() ->
1068    disconnect('_').
1069
1070disconnect(Code) ->
1071    {'or',[#ssh_msg_disconnect{code = Code,
1072			       _='_'},
1073	   tcp_closed,
1074	   {tcp_error,econnaborted}
1075	  ]}.
1076