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