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