diff options
Diffstat (limited to 'lib/ssh/src/ssh_message.erl')
-rw-r--r-- | lib/ssh/src/ssh_message.erl | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl new file mode 100644 index 0000000000..8d6c77c0ed --- /dev/null +++ b/lib/ssh/src/ssh_message.erl @@ -0,0 +1,533 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights 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, encode_host_key/1, decode_keyboard_interactive_prompts/2]). + +encode(#ssh_msg_global_request{ + name = Name, + want_reply = Bool, + data = Data}) -> + ssh_bits:encode([?SSH_MSG_GLOBAL_REQUEST, + Name, Bool, Data], [byte, string, boolean, '...']); +encode(#ssh_msg_request_success{data = Data}) -> + <<?BYTE(?SSH_MSG_REQUEST_SUCCESS), Data/binary>>; +encode(#ssh_msg_request_failure{}) -> + <<?BYTE(?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 + }) -> + ssh_bits:encode([?SSH_MSG_CHANNEL_OPEN, + Type, Sender, Window, Max, Data], [byte, string, uint32, + uint32, uint32, '...']); +encode(#ssh_msg_channel_open_confirmation{ + recipient_channel = Recipient, + sender_channel = Sender, + initial_window_size = InitWindowSize, + maximum_packet_size = MaxPacketSize, + data = Data + }) -> + ssh_bits:encode([?SSH_MSG_CHANNEL_OPEN_CONFIRMATION, Recipient, + Sender, InitWindowSize, MaxPacketSize, Data], + [byte, uint32, uint32, uint32, uint32, '...']); +encode(#ssh_msg_channel_open_failure{ + recipient_channel = Recipient, + reason = Reason, + description = Desc, + lang = Lang + }) -> + ssh_bits:encode([?SSH_MSG_CHANNEL_OPEN_FAILURE, Recipient, + Reason, Desc, Lang], [byte, uint32, uint32, string, string]); +encode(#ssh_msg_channel_window_adjust{ + recipient_channel = Recipient, + bytes_to_add = Bytes + }) -> + ssh_bits:encode([?SSH_MSG_CHANNEL_WINDOW_ADJUST, Recipient, Bytes], + [byte, uint32, uint32]); +encode(#ssh_msg_channel_data{ + recipient_channel = Recipient, + data = Data + }) -> + ssh_bits:encode([?SSH_MSG_CHANNEL_DATA, Recipient, Data], [byte, uint32, binary]); + +encode(#ssh_msg_channel_extended_data{ + recipient_channel = Recipient, + data_type_code = DataType, + data = Data + }) -> + ssh_bits:encode([?SSH_MSG_CHANNEL_EXTENDED_DATA, Recipient, + DataType, Data], [byte, uint32, uint32, binary]); + +encode(#ssh_msg_channel_eof{recipient_channel = Recipient + }) -> + <<?BYTE(?SSH_MSG_CHANNEL_EOF), ?UINT32(Recipient)>>; +encode(#ssh_msg_channel_close{ + recipient_channel = Recipient + }) -> + <<?BYTE(?SSH_MSG_CHANNEL_CLOSE), ?UINT32(Recipient)>>; +encode(#ssh_msg_channel_request{ + recipient_channel = Recipient, + request_type = Type, + want_reply = Bool, + data = Data + }) -> + ssh_bits:encode([?SSH_MSG_CHANNEL_REQUEST, Recipient, Type, Bool, Data], + [byte, uint32, string, boolean, '...']); +encode(#ssh_msg_channel_success{ + recipient_channel = Recipient + }) -> + <<?BYTE(?SSH_MSG_CHANNEL_SUCCESS), ?UINT32(Recipient)>>; +encode(#ssh_msg_channel_failure{ + recipient_channel = Recipient + }) -> + <<?BYTE(?SSH_MSG_CHANNEL_FAILURE), ?UINT32(Recipient)>>; + +encode(#ssh_msg_userauth_request{ + user = User, + service = Service, + method = Method, + data = Data + }) -> + ssh_bits:encode([?SSH_MSG_USERAUTH_REQUEST, User, Service, Method, Data], + [byte, string_utf8, string, string, '...']); +encode(#ssh_msg_userauth_failure{ + authentications = Auths, + partial_success = Bool + }) -> + ssh_bits:encode([?SSH_MSG_USERAUTH_FAILURE, Auths, Bool], + [byte, string, boolean]); +encode(#ssh_msg_userauth_success{}) -> + <<?BYTE(?SSH_MSG_USERAUTH_SUCCESS)>>; + +encode(#ssh_msg_userauth_banner{ + message = Banner, + language = Lang + }) -> + ssh_bits:encode([?SSH_MSG_USERAUTH_BANNER, Banner, Lang], + [byte, string_utf8, string]); + +encode(#ssh_msg_userauth_pk_ok{ + algorithm_name = Alg, + key_blob = KeyBlob + }) -> + ssh_bits:encode([?SSH_MSG_USERAUTH_PK_OK, Alg, KeyBlob], + [byte, string, binary]); + +encode(#ssh_msg_userauth_passwd_changereq{prompt = Prompt, + languge = Lang + })-> + ssh_bits:encode([?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, Prompt, Lang], + [byte, string, string]); + +encode(#ssh_msg_userauth_info_request{ + name = Name, + instruction = Inst, + language_tag = Lang, + num_prompts = NumPromtps, + data = Data}) -> + ssh_bits:encode([?SSH_MSG_USERAUTH_INFO_REQUEST, Name, Inst, Lang, NumPromtps, Data], + [byte, string, string, string, uint32, '...']); + +encode(#ssh_msg_userauth_info_response{ + num_responses = Num, + data = Data}) -> + ssh_bits:encode([?SSH_MSG_USERAUTH_INFO_RESPONSE, Num, Data], + [byte, uint32, '...']); +encode(#ssh_msg_disconnect{ + code = Code, + description = Desc, + language = Lang + }) -> + ssh_bits:encode([?SSH_MSG_DISCONNECT, Code, Desc, Lang], + [byte, uint32, string, string]); + +encode(#ssh_msg_service_request{ + name = Service + }) -> + ssh_bits:encode([?SSH_MSG_SERVICE_REQUEST, Service], [byte, string]); + +encode(#ssh_msg_service_accept{ + name = Service + }) -> + ssh_bits:encode([?SSH_MSG_SERVICE_ACCEPT, Service], [byte, string]); + +encode(#ssh_msg_newkeys{}) -> + <<?BYTE(?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 + }) -> + ssh_bits:encode([?SSH_MSG_KEXINIT, Cookie, KeyAlgs, HostKeyAlgs, EncAlgC2S, EncAlgS2C, + MacAlgC2S, MacAlgS2C, CompAlgS2C, CompAlgC2S, LangC2S, LangS2C, Bool, + Reserved], + [byte, cookie, + name_list, name_list, + name_list, name_list, + name_list, name_list, + name_list, name_list, + name_list, name_list, + boolean, uint32]); + +encode(#ssh_msg_kexdh_init{e = E}) -> + ssh_bits:encode([?SSH_MSG_KEXDH_INIT, E], [byte, mpint]); + +encode(#ssh_msg_kexdh_reply{ + public_host_key = Key, + f = F, + h_sig = Signature + }) -> + EncKey = encode_host_key(Key), + EncSign = encode_sign(Key, Signature), + ssh_bits:encode([?SSH_MSG_KEXDH_REPLY, EncKey, F, EncSign], [byte, binary, mpint, binary]); + +encode(#ssh_msg_kex_dh_gex_request{ + min = Min, + n = N, + max = Max + }) -> + ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_REQUEST, Min, N, Max], + [byte, uint32, uint32, uint32, uint32]); +encode(#ssh_msg_kex_dh_gex_request_old{n = N}) -> + ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_REQUEST_OLD, N], + [byte, uint32]); + +encode(#ssh_msg_kex_dh_gex_group{p = Prime, g = Generator}) -> + ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_GROUP, Prime, Generator], + [byte, mpint, mpint]); + +encode(#ssh_msg_kex_dh_gex_init{e = Public}) -> + ssh_bits:encode([?SSH_MSG_KEX_DH_GEX_INIT, Public], [byte, mpint]); + +encode(#ssh_msg_kex_dh_gex_reply{ + %% Will be private key encode_host_key extracts only the public part! + public_host_key = Key, + f = F, + h_sig = Signature + }) -> + EncKey = encode_host_key(Key), + EncSign = encode_sign(Key, Signature), + ssh_bits:encode([?SSH_MSG_KEXDH_REPLY, EncKey, F, EncSign], [byte, binary, mpint, binary]); + +encode(#ssh_msg_ignore{data = Data}) -> + ssh_bits:encode([?SSH_MSG_IGNORE, Data], [byte, string]); + +encode(#ssh_msg_unimplemented{sequence = Seq}) -> + ssh_bits:encode([?SSH_MSG_IGNORE, Seq], [byte, uint32]); + +encode(#ssh_msg_debug{always_display = Bool, + message = Msg, + language = Lang}) -> + ssh_bits:encode([?SSH_MSG_DEBUG, Bool, Msg, Lang], [byte, boolean, string, string]). + + +%% Connection Messages +decode(<<?BYTE(?SSH_MSG_GLOBAL_REQUEST), ?UINT32(Len), Name:Len/binary, + ?BYTE(Bool), Data/binary>>) -> + #ssh_msg_global_request{ + name = Name, + want_reply = erl_boolean(Bool), + data = Data + }; +decode(<<?BYTE(?SSH_MSG_REQUEST_SUCCESS), Data/binary>>) -> + #ssh_msg_request_success{data = Data}; +decode(<<?BYTE(?SSH_MSG_REQUEST_FAILURE)>>) -> + #ssh_msg_request_failure{}; +decode(<<?BYTE(?SSH_MSG_CHANNEL_OPEN), + ?UINT32(Len), Type:Len/binary, + ?UINT32(Sender), ?UINT32(Window), ?UINT32(Max), + Data/binary>>) -> + #ssh_msg_channel_open{ + channel_type = binary_to_list(Type), + sender_channel = Sender, + initial_window_size = Window, + maximum_packet_size = Max, + data = Data + }; +decode(<<?BYTE(?SSH_MSG_CHANNEL_OPEN_CONFIRMATION), ?UINT32(Recipient), ?UINT32(Sender), + ?UINT32(InitWindowSize), ?UINT32(MaxPacketSize), + Data/binary>>) -> + #ssh_msg_channel_open_confirmation{ + recipient_channel = Recipient, + sender_channel = Sender, + initial_window_size = InitWindowSize, + maximum_packet_size = MaxPacketSize, + data = Data + }; +decode(<<?BYTE(?SSH_MSG_CHANNEL_OPEN_FAILURE), ?UINT32(Recipient), ?UINT32(Reason), + ?UINT32(Len0), Desc:Len0/binary, ?UINT32(Len1), Lang:Len1/binary >>) -> + #ssh_msg_channel_open_failure{ + recipient_channel = Recipient, + reason = Reason, + description = unicode:characters_to_list(Desc), + lang = Lang + }; +decode(<<?BYTE(?SSH_MSG_CHANNEL_WINDOW_ADJUST), ?UINT32(Recipient), ?UINT32(Bytes)>>) -> + #ssh_msg_channel_window_adjust{ + recipient_channel = Recipient, + bytes_to_add = Bytes + }; + +decode(<<?BYTE(?SSH_MSG_CHANNEL_DATA), ?UINT32(Recipient), ?UINT32(Len), Data:Len/binary>>) -> + #ssh_msg_channel_data{ + recipient_channel = Recipient, + data = Data + }; +decode(<<?BYTE(?SSH_MSG_CHANNEL_EXTENDED_DATA), ?UINT32(Recipient), + ?UINT32(DataType), ?UINT32(Len), Data:Len/binary>>) -> + #ssh_msg_channel_extended_data{ + recipient_channel = Recipient, + data_type_code = DataType, + data = Data + }; +decode(<<?BYTE(?SSH_MSG_CHANNEL_EOF), ?UINT32(Recipient)>>) -> + #ssh_msg_channel_eof{ + recipient_channel = Recipient + }; +decode(<<?BYTE(?SSH_MSG_CHANNEL_CLOSE), ?UINT32(Recipient)>>) -> + #ssh_msg_channel_close{ + recipient_channel = Recipient + }; +decode(<<?BYTE(?SSH_MSG_CHANNEL_REQUEST), ?UINT32(Recipient), + ?UINT32(Len), RequestType:Len/binary, + ?BYTE(Bool), Data/binary>>) -> + #ssh_msg_channel_request{ + recipient_channel = Recipient, + request_type = unicode:characters_to_list(RequestType), + want_reply = erl_boolean(Bool), + data = Data + }; +decode(<<?BYTE(?SSH_MSG_CHANNEL_SUCCESS), ?UINT32(Recipient)>>) -> + #ssh_msg_channel_success{ + recipient_channel = Recipient + }; +decode(<<?BYTE(?SSH_MSG_CHANNEL_FAILURE), ?UINT32(Recipient)>>) -> + #ssh_msg_channel_failure{ + recipient_channel = Recipient + }; + +%%% Auth Messages +decode(<<?BYTE(?SSH_MSG_USERAUTH_REQUEST), + ?UINT32(Len0), User:Len0/binary, + ?UINT32(Len1), Service:Len1/binary, + ?UINT32(Len2), Method:Len2/binary, + Data/binary>>) -> + #ssh_msg_userauth_request{ + user = unicode:characters_to_list(User), + service = unicode:characters_to_list(Service), + method = unicode:characters_to_list(Method), + data = Data + }; + +decode(<<?BYTE(?SSH_MSG_USERAUTH_FAILURE), + ?UINT32(Len0), Auths:Len0/binary, + ?BYTE(Bool)>>) -> + #ssh_msg_userauth_failure { + authentications = unicode:characters_to_list(Auths), + partial_success = erl_boolean(Bool) + }; + +decode(<<?BYTE(?SSH_MSG_USERAUTH_SUCCESS)>>) -> + #ssh_msg_userauth_success{}; + +decode(<<?BYTE(?SSH_MSG_USERAUTH_BANNER), + ?UINT32(Len0), Banner:Len0/binary, + ?UINT32(Len1), Lang:Len1/binary>>) -> + #ssh_msg_userauth_banner{ + message = Banner, + language = Lang + }; + +decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST), ?UINT32(Len0), Name:Len0/binary, + ?UINT32(Len1), Inst:Len1/binary, ?UINT32(Len2), Lang:Len2/binary, + ?UINT32(NumPromtps), Data/binary>>) -> + #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(<<?BYTE(?SSH_MSG_USERAUTH_PK_OK), ?UINT32(Len), Alg:Len/binary, KeyBlob/binary>>) -> + #ssh_msg_userauth_pk_ok{ + algorithm_name = Alg, + key_blob = KeyBlob + }; + +%%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST: +decode(<<?BYTE(?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ), ?UINT32(Len0), Prompt:Len0/binary, + ?UINT32(Len1), Lang:Len1/binary>>) -> + #ssh_msg_userauth_passwd_changereq{ + prompt = Prompt, + languge = Lang + }; + +decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) -> + #ssh_msg_userauth_info_response{ + num_responses = Num, + data = Data}; + +%%% Keyexchange messages +decode(<<?BYTE(?SSH_MSG_KEXINIT), Cookie:128, Data/binary>>) -> + decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10); + +decode(<<?BYTE(?SSH_MSG_KEXDH_INIT), ?UINT32(Len), E:Len/binary>>) -> + #ssh_msg_kexdh_init{e = erlint(Len, E) + }; +decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST), ?UINT32(Min), ?UINT32(N), ?UINT32(Max)>>) -> + #ssh_msg_kex_dh_gex_request{ + min = Min, + n = N, + max = Max + }; +decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST_OLD), ?UINT32(N)>>) -> + #ssh_msg_kex_dh_gex_request_old{ + n = N + }; +decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP), + ?UINT32(Len0), Prime:Len0/big-signed-integer-unit:8, + ?UINT32(Len1), Generator:Len1/big-signed-integer-unit:8>>) -> + #ssh_msg_kex_dh_gex_group{ + p = Prime, + g = Generator + }; +decode(<<?BYTE(?SSH_MSG_KEXDH_REPLY), ?UINT32(Len0), Key:Len0/binary, + ?UINT32(Len1), F:Len1/binary, + ?UINT32(Len2), Hashsign:Len2/binary>>) -> + #ssh_msg_kexdh_reply{ + public_host_key = decode_host_key(Key), + f = erlint(Len1, F), + h_sig = decode_sign(Hashsign) + }; + +decode(<<?SSH_MSG_SERVICE_REQUEST, ?UINT32(Len0), Service:Len0/binary>>) -> + #ssh_msg_service_request{ + name = unicode:characters_to_list(Service) + }; + +decode(<<?SSH_MSG_SERVICE_ACCEPT, ?UINT32(Len0), Service:Len0/binary>>) -> + #ssh_msg_service_accept{ + name = unicode:characters_to_list(Service) + }; + +decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code), + ?UINT32(Len0), Desc:Len0/binary, ?UINT32(Len1), Lang:Len1/binary>>) -> + #ssh_msg_disconnect{ + code = Code, + description = unicode:characters_to_list(Desc), + language = Lang + }; + +decode(<<?SSH_MSG_NEWKEYS>>) -> + #ssh_msg_newkeys{}; + +decode(<<?BYTE(?SSH_MSG_IGNORE), Data/binary>>) -> + #ssh_msg_ignore{data = Data}; + +decode(<<?BYTE(?SSH_MSG_UNIMPLEMENTED), ?UINT32(Seq)>>) -> + #ssh_msg_unimplemented{sequence = Seq}; + +decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?UINT32(Len0), Msg:Len0/binary, + ?UINT32(Len1), Lang:Len1/binary>>) -> + #ssh_msg_debug{always_display = erl_boolean(Bool), + message = Msg, + language = Lang}. + +decode_keyboard_interactive_prompts(<<>>, Acc) -> + lists:reverse(Acc); +decode_keyboard_interactive_prompts(<<?UINT32(Len), Prompt:Len/binary, ?BYTE(Bool), Bin/binary>>, + Acc) -> + decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]). + +erl_boolean(0) -> + false; +erl_boolean(1) -> + true. + +decode_kex_init(<<?BYTE(Bool), ?UINT32(X)>>, Acc, 0) -> + list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc])); +decode_kex_init(<<?UINT32(Len), Data:Len/binary, Rest/binary>>, Acc, N) -> + Names = string:tokens(unicode:characters_to_list(Data), ","), + decode_kex_init(Rest, [Names | Acc], N -1). + +erlint(MPIntSize, MPIntValue) -> + Bits = MPIntSize * 8, + <<Integer:Bits/integer>> = MPIntValue, + Integer. + +decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) -> + Signature. + +decode_host_key(<<?UINT32(Len), Alg:Len/binary, Rest/binary>>) -> + decode_host_key(Alg, Rest). + +decode_host_key(<<"ssh-rsa">>, <<?UINT32(Len0), E:Len0/binary, + ?UINT32(Len1), N:Len1/binary>>) -> + #'RSAPublicKey'{publicExponent = erlint(Len0, E), + modulus = erlint(Len1, N)}; + +decode_host_key(<<"ssh-dss">>, + <<?UINT32(Len0), P:Len0/binary, + ?UINT32(Len1), Q:Len1/binary, + ?UINT32(Len2), G:Len2/binary, + ?UINT32(Len3), Y:Len3/binary>>) -> + {erlint(Len3, Y), #'Dss-Parms'{p = erlint(Len0, P), q = erlint(Len1, Q), + g = erlint(Len2, G)}}. + +encode_host_key(#'RSAPublicKey'{modulus = N, publicExponent = E}) -> + ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]); +encode_host_key({Y, #'Dss-Parms'{p = P, q = Q, g = G}}) -> + ssh_bits:encode(["ssh-dss", P, Q, G, Y], + [string, mpint, mpint, mpint, mpint]); +encode_host_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) -> + ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]); +encode_host_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) -> + ssh_bits:encode(["ssh-dss", P, Q, G, Y], + [string, mpint, mpint, mpint, mpint]). +encode_sign(#'RSAPrivateKey'{}, Signature) -> + ssh_bits:encode(["ssh-rsa", Signature],[string, binary]); +encode_sign(#'DSAPrivateKey'{}, Signature) -> + ssh_bits:encode(["ssh-dss", Signature],[string, binary]). |