%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% %%------------------------------------------------------------------ -module(ssh_message). -include_lib("public_key/include/public_key.hrl"). -include("ssh.hrl"). -include("ssh_connect.hrl"). -include("ssh_auth.hrl"). -include("ssh_transport.hrl"). -export([encode/1, decode/1, decode_keyboard_interactive_prompts/2]). -define('2bin'(X), (if is_binary(X) -> X; is_list(X) -> list_to_binary(X); X==undefined -> <<>> end) ). -define('E...'(X), ?'2bin'(X)/binary ). -define(Eboolean(X), ?BOOLEAN(case X of true -> ?TRUE; false -> ?FALSE end) ). -define(Ebyte(X), ?BYTE(X) ). -define(Euint32(X), ?UINT32(X) ). -define(Estring(X), ?STRING(?'2bin'(X)) ). -define(Estring_utf8(X), ?string_utf8(X)/binary ). -define(Ename_list(X), ?STRING(ssh_bits:name_list(X)) ). -define(Empint(X), (ssh_bits:mpint(X))/binary ). -define(Ebinary(X), ?STRING(X) ). ucl(B) -> try unicode:characters_to_list(B) of L when is_list(L) -> L; {error,_Matched,Rest} -> throw({error,{bad_unicode,Rest}}) catch _:_ -> throw({error,bad_unicode}) end. -define(unicode_list(B), ucl(B)). encode(#ssh_msg_global_request{ name = Name, want_reply = Bool, data = Data}) -> <>; encode(#ssh_msg_request_success{data = Data}) -> <>; encode(#ssh_msg_request_failure{}) -> <>; encode(#ssh_msg_channel_open{ channel_type = Type, sender_channel = Sender, initial_window_size = Window, maximum_packet_size = Max, data = Data }) -> <>; encode(#ssh_msg_channel_open_confirmation{ recipient_channel = Recipient, sender_channel = Sender, initial_window_size = InitWindowSize, maximum_packet_size = MaxPacketSize, data = Data }) -> <>; encode(#ssh_msg_channel_open_failure{ recipient_channel = Recipient, reason = Reason, description = Desc, lang = Lang }) -> <>; encode(#ssh_msg_channel_window_adjust{ recipient_channel = Recipient, bytes_to_add = Bytes }) -> <>; encode(#ssh_msg_channel_data{ recipient_channel = Recipient, data = Data }) -> <>; encode(#ssh_msg_channel_extended_data{ recipient_channel = Recipient, data_type_code = DataType, data = Data }) -> <>; encode(#ssh_msg_channel_eof{recipient_channel = Recipient }) -> <>; encode(#ssh_msg_channel_close{ recipient_channel = Recipient }) -> <>; encode(#ssh_msg_channel_request{ recipient_channel = Recipient, request_type = Type, want_reply = Bool, data = Data }) -> <>; encode(#ssh_msg_channel_success{ recipient_channel = Recipient }) -> <>; encode(#ssh_msg_channel_failure{ recipient_channel = Recipient }) -> <>; encode(#ssh_msg_userauth_request{ user = User, service = Service, method = Method, data = Data }) -> <>; encode(#ssh_msg_userauth_failure{ authentications = Auths, partial_success = Bool }) -> <>; encode(#ssh_msg_userauth_success{}) -> <>; encode(#ssh_msg_userauth_banner{ message = Banner, language = Lang }) -> <>; encode(#ssh_msg_userauth_pk_ok{ algorithm_name = Alg, key_blob = KeyBlob }) -> <>; encode(#ssh_msg_userauth_passwd_changereq{prompt = Prompt, languge = Lang })-> <>; encode(#ssh_msg_userauth_info_request{ name = Name, instruction = Inst, language_tag = Lang, num_prompts = NumPromtps, data = Data}) -> <>; encode(#ssh_msg_userauth_info_response{ num_responses = Num, data = Data}) -> lists:foldl(fun %%("", Acc) -> Acc; % commented out since it seem wrong (Response, Acc) -> <> end, <>, Data); encode(#ssh_msg_disconnect{ code = Code, description = Desc, language = Lang }) -> <>; encode(#ssh_msg_service_request{ name = Service }) -> <>; encode(#ssh_msg_service_accept{ name = Service }) -> <>; encode(#ssh_msg_ext_info{ nr_extensions = N, data = Data }) -> lists:foldl(fun({ExtName,ExtVal}, Acc) -> <> end, <>, Data); encode(#ssh_msg_newkeys{}) -> <>; encode(#ssh_msg_kexinit{ cookie = Cookie, kex_algorithms = KeyAlgs, server_host_key_algorithms = HostKeyAlgs, encryption_algorithms_client_to_server = EncAlgC2S, encryption_algorithms_server_to_client = EncAlgS2C, mac_algorithms_client_to_server = MacAlgC2S, mac_algorithms_server_to_client = MacAlgS2C, compression_algorithms_client_to_server = CompAlgS2C, compression_algorithms_server_to_client = CompAlgC2S, languages_client_to_server = LangC2S, languages_server_to_client = LangS2C, first_kex_packet_follows = Bool, reserved = Reserved }) -> <>; encode(#ssh_msg_kexdh_init{e = E}) -> <>; encode(#ssh_msg_kexdh_reply{ public_host_key = {Key,SigAlg}, f = F, h_sig = Signature }) -> EncKey = public_key:ssh_encode(Key, ssh2_pubkey), EncSign = encode_signature(Key, SigAlg, Signature), <>; encode(#ssh_msg_kex_dh_gex_request{ min = Min, n = N, max = Max }) -> <>; encode(#ssh_msg_kex_dh_gex_request_old{n = N}) -> <>; encode(#ssh_msg_kex_dh_gex_group{p = Prime, g = Generator}) -> <>; encode(#ssh_msg_kex_dh_gex_init{e = Public}) -> <>; encode(#ssh_msg_kex_dh_gex_reply{ %% Will be private key encode_host_key extracts only the public part! public_host_key = {Key,SigAlg}, f = F, h_sig = Signature }) -> EncKey = public_key:ssh_encode(Key, ssh2_pubkey), EncSign = encode_signature(Key, SigAlg, Signature), <>; encode(#ssh_msg_kex_ecdh_init{q_c = Q_c}) -> <>; encode(#ssh_msg_kex_ecdh_reply{public_host_key = {Key,SigAlg}, q_s = Q_s, h_sig = Sign}) -> EncKey = public_key:ssh_encode(Key, ssh2_pubkey), EncSign = encode_signature(Key, SigAlg, Sign), <>; encode(#ssh_msg_ignore{data = Data}) -> <>; encode(#ssh_msg_unimplemented{sequence = Seq}) -> <>; encode(#ssh_msg_debug{always_display = Bool, message = Msg, language = Lang}) -> <>. %% Connection Messages decode(<>) -> #ssh_msg_global_request{ name = Name, want_reply = erl_boolean(Bool), data = Data }; decode(<>) -> #ssh_msg_request_success{data = Data}; decode(<>) -> #ssh_msg_request_failure{}; decode(<>) -> #ssh_msg_channel_open{ channel_type = binary_to_list(Type), sender_channel = Sender, initial_window_size = Window, maximum_packet_size = Max, data = Data }; decode(<>) -> #ssh_msg_channel_open_confirmation{ recipient_channel = Recipient, sender_channel = Sender, initial_window_size = InitWindowSize, maximum_packet_size = MaxPacketSize, data = Data }; decode(<> ) -> #ssh_msg_channel_open_failure{ recipient_channel = Recipient, reason = Reason, description = ?unicode_list(Desc), lang = Lang }; decode(<>) -> #ssh_msg_channel_window_adjust{ recipient_channel = Recipient, bytes_to_add = Bytes }; decode(<>) -> #ssh_msg_channel_data{ recipient_channel = Recipient, data = Data }; decode(<>) -> #ssh_msg_channel_extended_data{ recipient_channel = Recipient, data_type_code = DataType, data = Data }; decode(<>) -> #ssh_msg_channel_eof{ recipient_channel = Recipient }; decode(<>) -> #ssh_msg_channel_close{ recipient_channel = Recipient }; decode(<>) -> #ssh_msg_channel_request{ recipient_channel = Recipient, request_type = ?unicode_list(RequestType), want_reply = erl_boolean(Bool), data = Data }; decode(<>) -> #ssh_msg_channel_success{ recipient_channel = Recipient }; decode(<>) -> #ssh_msg_channel_failure{ recipient_channel = Recipient }; %%% Auth Messages decode(<>) -> #ssh_msg_userauth_request{ user = ?unicode_list(User), service = ?unicode_list(Service), method = ?unicode_list(Method), data = Data }; decode(<>) -> #ssh_msg_userauth_failure { authentications = ?unicode_list(Auths), partial_success = erl_boolean(Bool) }; decode(<>) -> #ssh_msg_userauth_success{}; decode(<>) -> #ssh_msg_userauth_banner{ message = Banner, language = Lang }; decode(<>) -> #ssh_msg_userauth_info_request{ name = Name, instruction = Inst, language_tag = Lang, num_prompts = NumPromtps, data = Data}; %%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST: decode(<>) -> #ssh_msg_userauth_passwd_changereq{ prompt = Prompt, languge = Lang }; %%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST: decode(<>) -> #ssh_msg_userauth_pk_ok{ algorithm_name = Alg, key_blob = KeyBlob }; decode(<>) -> #ssh_msg_userauth_info_response{ num_responses = Num, data = Data}; decode(<>) -> Data = bin_foldr( fun(Bin,Acc) when length(Acc) == N -> {Bin,Acc}; (<>, Acc) -> {Rest,[{binary_to_list(V0),binary_to_list(V1)}|Acc]} end, [], BinData), #ssh_msg_ext_info{ nr_extensions = N, data = Data }; %%% Keyexchange messages decode(<>) -> decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10); decode(<<"dh",?BYTE(?SSH_MSG_KEXDH_INIT), ?DEC_MPINT(E,__0)>>) -> #ssh_msg_kexdh_init{e = E }; decode(<<"dh", ?BYTE(?SSH_MSG_KEXDH_REPLY), ?DEC_BIN(Key,__0), ?DEC_MPINT(F,__1), ?DEC_BIN(Hashsign,__2)>>) -> #ssh_msg_kexdh_reply{ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey), f = F, h_sig = decode_signature(Hashsign) }; decode(<>) -> #ssh_msg_kex_dh_gex_request{ min = Min, n = N, max = Max }; decode(<<"dh_gex",?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST_OLD), ?UINT32(N)>>) -> #ssh_msg_kex_dh_gex_request_old{ n = N }; decode(<<"dh_gex",?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP), ?DEC_MPINT(Prime,__0), ?DEC_MPINT(Generator,__1) >>) -> #ssh_msg_kex_dh_gex_group{ p = Prime, g = Generator }; decode(<>) -> #ssh_msg_kex_dh_gex_init{ e = E }; decode(<>) -> #ssh_msg_kex_dh_gex_reply{ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey), f = F, h_sig = decode_signature(Hashsign) }; decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_INIT), ?DEC_MPINT(Q_c,__0)>>) -> #ssh_msg_kex_ecdh_init{ q_c = Q_c }; decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_REPLY), ?DEC_BIN(Key,__1), ?DEC_MPINT(Q_s,__2), ?DEC_BIN(Sig,__3)>>) -> #ssh_msg_kex_ecdh_reply{ public_host_key = public_key:ssh_decode(Key, ssh2_pubkey), q_s = Q_s, h_sig = decode_signature(Sig) }; decode(<>) -> #ssh_msg_service_request{ name = ?unicode_list(Service) }; decode(<>) -> #ssh_msg_service_accept{ name = ?unicode_list(Service) }; decode(<>) -> #ssh_msg_disconnect{ code = Code, description = ?unicode_list(Desc), language = Lang }; %% Accept bad disconnects from ancient openssh clients that doesn't send language tag. Use english as a work-around. decode(<>) -> #ssh_msg_disconnect{ code = Code, description = ?unicode_list(Desc), language = <<"en">> }; decode(<>) -> #ssh_msg_newkeys{}; decode(<>) -> #ssh_msg_ignore{data = Data}; decode(<>) -> #ssh_msg_unimplemented{sequence = Seq}; decode(<>) -> #ssh_msg_debug{always_display = erl_boolean(Bool), message = Msg, language = Lang}. %%%================================================================ %%% %%% Helper functions %%% bin_foldr(Fun, Acc, Bin) -> lists:reverse(bin_foldl(Fun, Acc, Bin)). bin_foldl(_, Acc, <<>>) -> Acc; bin_foldl(Fun, Acc0, Bin0) -> {Bin,Acc} = Fun(Bin0,Acc0), bin_foldl(Fun, Acc, Bin). %%%---------------------------------------------------------------- decode_keyboard_interactive_prompts(<<>>, Acc) -> lists:reverse(Acc); decode_keyboard_interactive_prompts(<>, Acc) -> decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]). %%%---------------------------------------------------------------- erl_boolean(0) -> false; erl_boolean(1) -> true. %%%---------------------------------------------------------------- decode_kex_init(<>, Acc, 0) -> list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc])); decode_kex_init(<>, Acc, 0) -> %% The mandatory trailing UINT32 is missing. Assume the value it anyhow must have %% See rfc 4253 7.1 X = 0, list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc])); decode_kex_init(<>, Acc, N) -> Names = string:tokens(?unicode_list(Data), ","), decode_kex_init(Rest, [Names | Acc], N -1). %%%================================================================ %%% %%% Signature decode/encode %%% decode_signature(<>) -> {binary_to_list(Alg), Signature}. encode_signature(#'RSAPublicKey'{}, SigAlg, Signature) -> SignName = list_to_binary(atom_to_list(SigAlg)), <>; encode_signature({_, #'Dss-Parms'{}}, _SigAlg, Signature) -> <>), ?Ebinary(Signature)>>; encode_signature({#'ECPoint'{}, {namedCurve,OID}}, _SigAlg, Signature) -> CurveName = public_key:oid2ssh_curvename(OID), <>), ?Ebinary(Signature)>>.