1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20%%
21
22-module(ssh_eqc_encode_decode).
23
24-compile(export_all).
25
26-include_lib("common_test/include/ct_property_test.hrl").
27
28%% Public key records:
29-include_lib("public_key/include/public_key.hrl").
30
31%%% Properties:
32
33prop_ssh_decode() ->
34    ?FORALL({Msg,KexFam}, ?LET(KF, kex_family(), {ssh_msg(KF),KF} ),
35	    try ssh_message:decode(decode_state(Msg,KexFam))
36	    of
37		_ -> true
38	    catch
39
40		C:E -> io:format('~p:~p~n',[C,E]),
41		       false
42	    end
43	   ).
44
45
46%%% This fails because ssh_message is not symmetric in encode and decode regarding data types
47prop_ssh_decode_encode() ->
48    ?FORALL({Msg,KexFam}, ?LET(KF, kex_family(), {ssh_msg(KF),KF} ),
49	    Msg == ssh_message:encode(
50		     fix_asym(
51		       ssh_message:decode(decode_state(Msg,KexFam))))
52	   ).
53
54
55%%%================================================================
56%%%
57%%% Generators
58%%%
59
60ssh_msg(<<"dh">>) ->
61    ?LET(M,oneof(
62	     [
63	      [msg_code('SSH_MSG_KEXDH_INIT'),gen_mpint()],  % 30
64	      [msg_code('SSH_MSG_KEXDH_REPLY'),gen_pubkey_string(rsa),gen_mpint(),gen_signature_string(rsa)] % 31
65	      | rest_ssh_msgs()
66	     ]),
67	 list_to_binary(M));
68
69ssh_msg(<<"dh_gex">>) ->
70    ?LET(M,oneof(
71	     [
72	      [msg_code('SSH_MSG_KEX_DH_GEX_REQUEST_OLD'),gen_uint32()],  % 30
73	      [msg_code('SSH_MSG_KEX_DH_GEX_GROUP'),gen_mpint(),gen_mpint()]  % 31
74	      | rest_ssh_msgs()
75	     ]),
76	 list_to_binary(M));
77
78 ssh_msg(<<"ecdh">>) ->
79     ?LET(M,oneof(
80 	     [
81 	      [msg_code('SSH_MSG_KEX_ECDH_INIT'),gen_mpint()],   % 30
82	      [msg_code('SSH_MSG_KEX_ECDH_REPLY'),gen_pubkey_string(ecdsa),gen_mpint(),gen_signature_string(ecdsa)] % 31
83	      | rest_ssh_msgs()
84 	     ]),
85	 list_to_binary(M)).
86
87
88rest_ssh_msgs() ->
89    [%%                    SSH_MSG_USERAUTH_INFO_RESPONSE
90     %% hard args          SSH_MSG_USERAUTH_INFO_REQUEST
91     %% rfc4252 p12 error  SSH_MSG_USERAUTH_REQUEST
92     [msg_code('SSH_MSG_KEX_DH_GEX_REQUEST'),gen_uint32(),gen_uint32(),gen_uint32()],
93     [msg_code('SSH_MSG_KEX_DH_GEX_INIT'),gen_mpint()],
94     [msg_code('SSH_MSG_KEX_DH_GEX_REPLY'),gen_pubkey_string(rsa),gen_mpint(),gen_signature_string(rsa)],
95     [msg_code('SSH_MSG_CHANNEL_CLOSE'),gen_uint32()],
96     [msg_code('SSH_MSG_CHANNEL_DATA'),gen_uint32(),gen_string( )],
97     [msg_code('SSH_MSG_CHANNEL_EOF'),gen_uint32()],
98     [msg_code('SSH_MSG_CHANNEL_EXTENDED_DATA'),gen_uint32(),gen_uint32(),gen_string( )],
99     [msg_code('SSH_MSG_CHANNEL_FAILURE'),gen_uint32()],
100     [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("direct-tcpip"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32(),gen_string( ),gen_uint32()],
101     [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("forwarded-tcpip"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32(),gen_string( ),gen_uint32()],
102     [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("session"),gen_uint32(),gen_uint32(),gen_uint32()],
103     [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("x11"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32()],
104     [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string( ),gen_uint32(),gen_uint32(),gen_uint32()],
105     [msg_code('SSH_MSG_CHANNEL_OPEN_CONFIRMATION'),gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32()],
106     [msg_code('SSH_MSG_CHANNEL_OPEN_FAILURE'),gen_uint32(),gen_uint32(),gen_string( ),gen_string( )],
107     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("env"),gen_boolean(),gen_string( ),gen_string( )],
108     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exec"),gen_boolean(),gen_string( )],
109     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exit-signal"),0,gen_string( ),gen_boolean(),gen_string( ),gen_string( )],
110     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exit-status"),0,gen_uint32()],
111     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("pty-req"),gen_boolean(),gen_string( ),gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( )],
112     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("shell"),gen_boolean()],
113     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("signal"),0,gen_string( )],
114     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("subsystem"),gen_boolean(),gen_string( )],
115     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("window-change"),0,gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32()],
116     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("x11-req"),gen_boolean(),gen_boolean(),gen_string( ),gen_string( ),gen_uint32()],
117     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("xon-xoff"),0,gen_boolean()],
118     [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string( ),gen_boolean()],
119     [msg_code('SSH_MSG_CHANNEL_SUCCESS'),gen_uint32()],
120     [msg_code('SSH_MSG_CHANNEL_WINDOW_ADJUST'),gen_uint32(),gen_uint32()],
121     [msg_code('SSH_MSG_DEBUG'),gen_boolean(),gen_string( ),gen_string( )],
122     [msg_code('SSH_MSG_DISCONNECT'),gen_uint32(),gen_string( ),gen_string( )],
123     [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string("cancel-tcpip-forward"),gen_boolean(),gen_string( ),gen_uint32()],
124     [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string("tcpip-forward"),gen_boolean(),gen_string( ),gen_uint32()],
125     [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string( ),gen_boolean()],
126     [msg_code('SSH_MSG_IGNORE'),gen_string( )],
127     [msg_code('SSH_MSG_KEXINIT'),gen_byte(16),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_boolean(),gen_uint32()],
128     [msg_code('SSH_MSG_NEWKEYS')],
129     [msg_code('SSH_MSG_REQUEST_FAILURE')],
130     [msg_code('SSH_MSG_REQUEST_SUCCESS')],
131     [msg_code('SSH_MSG_REQUEST_SUCCESS'),gen_uint32()],
132     [msg_code('SSH_MSG_SERVICE_ACCEPT'),gen_string( )],
133     [msg_code('SSH_MSG_SERVICE_REQUEST'),gen_string( )],
134     [msg_code('SSH_MSG_UNIMPLEMENTED'),gen_uint32()],
135     [msg_code('SSH_MSG_USERAUTH_BANNER'),gen_string( ),gen_string( )],
136     [msg_code('SSH_MSG_USERAUTH_FAILURE'),gen_name_list(),gen_boolean()],
137     [msg_code('SSH_MSG_USERAUTH_PASSWD_CHANGEREQ'),gen_string( ),gen_string( )],
138     [msg_code('SSH_MSG_USERAUTH_PK_OK'),gen_string( ),gen_string( )],
139     [msg_code('SSH_MSG_USERAUTH_SUCCESS')]
140    ].
141
142kex_family() -> oneof([<<"dh">>, <<"dh_gex">>, <<"ecdh">>]).
143
144gen_boolean() -> choose(0,1).
145
146gen_byte() -> choose(0,255).
147
148gen_uint16() -> gen_byte(2).
149
150gen_uint32() -> gen_byte(4).
151
152gen_uint64() -> gen_byte(8).
153
154gen_byte(N) when N>0 -> [gen_byte() || _ <- lists:seq(1,N)].
155
156gen_char() -> choose($a,$z).
157
158gen_mpint() -> ?LET(I, largeint(), ssh_bits:mpint(I)).
159
160strip_0s([0|T]) -> strip_0s(T);
161strip_0s(X) -> X.
162
163
164gen_string() ->
165    ?LET(Size, choose(0,10),
166	 ?LET(Vector,vector(Size, gen_char()),
167	      gen_string(Vector)
168	     )).
169
170gen_string(S) when is_binary(S) -> gen_string(binary_to_list(S));
171gen_string(S) when is_list(S) -> uint32_to_list(length(S)) ++ S.
172
173gen_name_list() ->
174    ?LET(NumNames, choose(0,10),
175	 ?LET(L, [gen_name() || _ <- lists:seq(1,NumNames)],
176	      gen_string( string:join(L,"," ) )
177	)).
178
179gen_name() -> gen_string().
180
181uint32_to_list(I) ->  binary_to_list(<<I:32/unsigned-big-integer>>).
182
183gen_pubkey_string(Type) ->
184    PubKey = case Type of
185		 rsa -> #'RSAPublicKey'{modulus = 12345,publicExponent = 2};
186		 ecdsa -> {#'ECPoint'{point=[1,2,3,4,5]},
187			   {namedCurve,{1,2,840,10045,3,1,7}}} % 'secp256r1' nistp256
188	     end,
189    gen_string(public_key:ssh_encode(PubKey, ssh2_pubkey)).
190
191
192gen_signature_string(Type) ->
193    Signature = <<"hejhopp">>,
194    Id = case Type of
195	     rsa -> "ssh-rsa";
196	     ecdsa -> "ecdsa-sha2-nistp256"
197	 end,
198    gen_string(gen_string(Id) ++ gen_string(Signature)).
199
200-define(MSG_CODE(Name,Num),
201msg_code(Name) -> Num;
202msg_code(Num) -> Name
203).
204
205?MSG_CODE('SSH_MSG_USERAUTH_REQUEST',   50);
206?MSG_CODE('SSH_MSG_USERAUTH_FAILURE',   51);
207?MSG_CODE('SSH_MSG_USERAUTH_SUCCESS',   52);
208?MSG_CODE('SSH_MSG_USERAUTH_BANNER',   53);
209?MSG_CODE('SSH_MSG_USERAUTH_PK_OK',   60);
210?MSG_CODE('SSH_MSG_USERAUTH_PASSWD_CHANGEREQ',   60);
211?MSG_CODE('SSH_MSG_DISCONNECT',   1);
212?MSG_CODE('SSH_MSG_IGNORE',   2);
213?MSG_CODE('SSH_MSG_UNIMPLEMENTED',   3);
214?MSG_CODE('SSH_MSG_DEBUG',   4);
215?MSG_CODE('SSH_MSG_SERVICE_REQUEST',   5);
216?MSG_CODE('SSH_MSG_SERVICE_ACCEPT',   6);
217?MSG_CODE('SSH_MSG_KEXINIT',   20);
218?MSG_CODE('SSH_MSG_NEWKEYS',   21);
219?MSG_CODE('SSH_MSG_GLOBAL_REQUEST',   80);
220?MSG_CODE('SSH_MSG_REQUEST_SUCCESS',   81);
221?MSG_CODE('SSH_MSG_REQUEST_FAILURE',   82);
222?MSG_CODE('SSH_MSG_CHANNEL_OPEN',   90);
223?MSG_CODE('SSH_MSG_CHANNEL_OPEN_CONFIRMATION',   91);
224?MSG_CODE('SSH_MSG_CHANNEL_OPEN_FAILURE',   92);
225?MSG_CODE('SSH_MSG_CHANNEL_WINDOW_ADJUST',   93);
226?MSG_CODE('SSH_MSG_CHANNEL_DATA',   94);
227?MSG_CODE('SSH_MSG_CHANNEL_EXTENDED_DATA',   95);
228?MSG_CODE('SSH_MSG_CHANNEL_EOF',   96);
229?MSG_CODE('SSH_MSG_CHANNEL_CLOSE',   97);
230?MSG_CODE('SSH_MSG_CHANNEL_REQUEST',   98);
231?MSG_CODE('SSH_MSG_CHANNEL_SUCCESS',   99);
232?MSG_CODE('SSH_MSG_CHANNEL_FAILURE',   100);
233?MSG_CODE('SSH_MSG_USERAUTH_INFO_REQUEST',   60);
234?MSG_CODE('SSH_MSG_USERAUTH_INFO_RESPONSE',   61);
235?MSG_CODE('SSH_MSG_KEXDH_INIT', 30);
236?MSG_CODE('SSH_MSG_KEXDH_REPLY', 31);
237?MSG_CODE('SSH_MSG_KEX_DH_GEX_REQUEST_OLD',   30);
238?MSG_CODE('SSH_MSG_KEX_DH_GEX_REQUEST',   34);
239?MSG_CODE('SSH_MSG_KEX_DH_GEX_GROUP',   31);
240?MSG_CODE('SSH_MSG_KEX_DH_GEX_INIT',   32);
241?MSG_CODE('SSH_MSG_KEX_DH_GEX_REPLY', 33);
242?MSG_CODE('SSH_MSG_KEX_ECDH_INIT', 30);
243?MSG_CODE('SSH_MSG_KEX_ECDH_REPLY', 31).
244
245%%%====================================================
246%%%=== WARNING: Knowledge of the test object ahead! ===
247%%%====================================================
248
249%% SSH message records:
250-include_lib("ssh/src/ssh_connect.hrl").
251-include_lib("ssh/src/ssh_transport.hrl").
252
253%%% Encoding and decodeing is asymetric so out=binary in=string. Sometimes. :(
254-define(fix_asym_Xdh_reply(S),
255 fix_asym(#S{public_host_key = Key, h_sig = {Alg,Sig}} = M) ->
256      M#S{public_host_key = {Key, list_to_atom(Alg)}, h_sig = Sig}
257).
258
259
260fix_asym(#ssh_msg_global_request{name=N} = M) -> M#ssh_msg_global_request{name = binary_to_list(N)};
261fix_asym(#ssh_msg_debug{message=D,language=L} = M) -> M#ssh_msg_debug{message = binary_to_list(D),
262								      language = binary_to_list(L)};
263fix_asym(#ssh_msg_kexinit{cookie=C} = M) -> M#ssh_msg_kexinit{cookie = <<C:128>>};
264?fix_asym_Xdh_reply(ssh_msg_kexdh_reply);
265?fix_asym_Xdh_reply(ssh_msg_kex_dh_gex_reply);
266?fix_asym_Xdh_reply(ssh_msg_kex_ecdh_reply);
267fix_asym(M) -> M.
268
269
270%%% Message codes 30 and 31 are overloaded depending on kex family so arrange the decoder
271%%% input as the test object does
272decode_state(<<30,_/binary>>=Msg, KexFam) -> <<KexFam/binary, Msg/binary>>;
273decode_state(<<31,_/binary>>=Msg, KexFam) -> <<KexFam/binary, Msg/binary>>;
274decode_state(Msg, _) -> Msg.
275
276