1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14
%% AB. All Rights Reserved.''
19
%%% Description: SSH transport protocol
21
-module(ssh_transport).
23
-import(lists, [reverse/1, map/2, foreach/2, foldl/3]).
26
-include_lib("kernel/include/inet.hrl").
28
-export([connect/1, connect/2, connect/3, close/1]).
29
-export([listen/2, listen/3, listen/4, stop_listener/1]).
30
-export([debug/2, debug/3, debug/4]).
32
-export([disconnect/2, disconnect/3, disconnect/4]).
34
-export([client_init/4, server_init/4]).
36
-export([ssh_init/3]). % , server_hello/4]).
38
-export([service_request/2, service_accept/2]).
40
-export([get_session_id/1]).
41
-export([peername/1]).
44
-export([yes_no/2, read_password/2]).
47
-define(DEFAULT_TIMEOUT, 5000).
50
-define(DBG_ALG, true).
51
-define(DBG_KEX, false).
52
-define(DBG_CRYPTO, false).
53
-define(DBG_PACKET, false).
54
-define(DBG_MESSAGE, true).
55
-define(DBG_BIN_MESSAGE, false).
56
-define(DBG_MAC, false).
57
-define(DBG_ZLIB, true).
75
state, %% what it's waiting for
77
role, %% client | server
78
peer, %% string version of peer address
80
c_vsn, %% client version {Major,Minor}
81
s_vsn, %% server version {Major,Minor}
83
c_version, %% client version string
84
s_version, %% server version string
86
c_keyinit, %% binary payload of kexinit packet
87
s_keyinit, %% binary payload of kexinit packet
89
algorithms, %% new algorithms (SSH_MSG_KEXINIT)
91
kex, %% key exchange algorithm
92
hkey, %% host key algorithm
93
key_cb, %% Private/Public key callback module
94
io_cb, %% Interaction callback module
96
send_mac=none, %% send MAC algorithm
97
send_mac_key, %% key used in send MAC algorithm
100
recv_mac=none, %% recv MAC algorithm
101
recv_mac_key, %% key used in recv MAC algorithm
104
encrypt = none, %% encrypt algorithm
105
encrypt_keys, %% encrypt keys
106
encrypt_block_size = 8,
108
decrypt = none, %% decrypt algorithm
109
decrypt_keys, %% decrypt keys
110
decrypt_block_size = 8,
115
c_lng=none, %% client to server languages
116
s_lng=none, %% server to client languages
118
user_ack = true, %% client
121
shared_secret, %% K from key exchange
122
exchanged_hash, %% H from key exchange
123
session_id, %% same as FIRST exchanged_hash
129
transport_messages() ->
130
[ {ssh_msg_disconnect, ?SSH_MSG_DISCONNECT,
131
[uint32,string,string]},
133
{ssh_msg_ignore, ?SSH_MSG_IGNORE,
136
{ssh_msg_unimplemented, ?SSH_MSG_UNIMPLEMENTED,
139
{ssh_msg_debug, ?SSH_MSG_DEBUG,
140
[boolean, string, string]},
142
{ssh_msg_service_request, ?SSH_MSG_SERVICE_REQUEST,
145
{ssh_msg_service_accept, ?SSH_MSG_SERVICE_ACCEPT,
148
{ssh_msg_kexinit, ?SSH_MSG_KEXINIT,
150
name_list, name_list,
151
name_list, name_list,
152
name_list, name_list,
153
name_list, name_list,
154
name_list, name_list,
158
{ssh_msg_newkeys, ?SSH_MSG_NEWKEYS,
164
[ {ssh_msg_kexdh_init, ?SSH_MSG_KEXDH_INIT,
167
{ssh_msg_kexdh_reply, ?SSH_MSG_KEXDH_REPLY,
168
[binary, mpint, binary]}
172
kex_dh_gex_messages() ->
173
[ {ssh_msg_kex_dh_gex_request, ?SSH_MSG_KEX_DH_GEX_REQUEST,
174
[uint32, uint32, uint32]},
176
{ssh_msg_kex_dh_gex_request_old, ?SSH_MSG_KEX_DH_GEX_REQUEST_OLD,
179
{ssh_msg_kex_dh_gex_group, ?SSH_MSG_KEX_DH_GEX_GROUP,
182
{ssh_msg_kex_dh_gex_init, ?SSH_MSG_KEX_DH_GEX_INIT,
185
{ssh_msg_kex_dh_gex_reply, ?SSH_MSG_KEX_DH_GEX_REPLY,
186
[binary, mpint, binary]}
189
yes_no(SSH, Prompt) when pid(SSH) ->
190
{ok, CB} = call(SSH, {get_cb, io}),
192
yes_no(SSH, Prompt) when record(SSH,ssh) ->
193
(SSH#ssh.io_cb):yes_no(Prompt).
195
read_password(SSH, Prompt) when pid(SSH) ->
196
{ok, CB} = call(SSH, {get_cb, io}),
197
CB:read_password(Prompt);
198
read_password(SSH, Prompt) when record(SSH,ssh) ->
199
(SSH#ssh.io_cb):read_password(Prompt).
201
%% read_line(SSH, Prompt) when pid(SSH) ->
202
%% {ok, CB} = call(SSH, {get_cb, io}),
203
%% CB:read_line(Prompt);
204
%% read_line(SSH, Prompt) when record(SSH,ssh) ->
205
%% (SSH#ssh.io_cb):read_line(Prompt).
212
SSH ! {ssh_call, [self()|Ref], Req},
221
connect(Host, Opts) ->
222
connect(Host, 22, Opts).
224
connect(Host,Port,Opts) ->
225
Pid = spawn_link(?MODULE, client_init, [self(), Host, Port, Opts]),
231
listen(UserFun, Port) ->
232
listen(UserFun, Port, []).
234
listen(UserFun, Port, Opts) ->
235
listen(UserFun, any, Port, Opts).
237
listen(UserFun, Addr, Port, Opts) ->
238
case spawn_link(?MODULE, server_init, [UserFun, Addr, Port, Opts]) of
239
Pid when is_pid(Pid) -> {ok, Pid};
243
stop_listener(Pid) when is_pid(Pid) ->
244
Ref = erlang:monitor(process, Pid),
247
{'DOWN', Ref, process, Pid, normal} ->
249
{'DOWN', Ref, process, Pid, Error} ->
255
debug(SSH, Message) ->
256
debug(SSH, true, Message, "en").
258
debug(SSH, Message, Lang) ->
259
debug(SSH, true, Message, Lang).
261
debug(SSH, Display, Message, Lang) ->
262
SSH ! {ssh_msg, self(), #ssh_msg_debug { always_display = Display,
267
SSH ! {ssh_msg, self(), #ssh_msg_ignore { data = Data }}.
269
disconnect(SSH, Code) ->
270
disconnect(SSH, Code, "", "").
272
disconnect(SSH, Code, _Msg) ->
273
disconnect(SSH, Code, "", "").
275
disconnect(SSH, Code, Msg, Lang) ->
276
SSH ! {ssh_msg, self(), #ssh_msg_disconnect { code = Code,
284
service_accept(SSH, Name) ->
285
SSH ! {ssh_msg, self(), #ssh_msg_service_accept { name = Name }}.
287
service_request(SSH, Name) ->
288
SSH ! {ssh_msg, self(), #ssh_msg_service_request { name = Name}},
290
{ssh_msg, SSH, R} when record(R, ssh_msg_service_accept) ->
292
{ssh_msg, SSH, R} when record(R, ssh_msg_disconnect) ->
299
client_init(User, Host, Port, Opts) ->
300
IfAddr = proplists:get_value(ifaddr, Opts, any),
301
Tmo = proplists:get_value(connect_timeout, Opts, ?DEFAULT_TIMEOUT),
302
NoDelay= proplists:get_value(tcp_nodelay, Opts, false),
303
case gen_tcp:connect(Host, Port, [{packet,line},
306
{ifaddr,IfAddr}], Tmo) of
308
SSH = ssh_init(S, client, Opts),
309
Peer = if tuple(Host) -> inet_parse:ntoa(Host);
310
atom(Host) -> atom_to_list(Host);
313
case client_hello(S, User, SSH#ssh { peer = Peer }, Tmo) of
315
User ! {self(), {error, E}};
320
User ! {self(), Error}
324
server_init(UserFun, Addr, Port, Opts) ->
326
SSH = ssh_init(S, server, Opts),
328
User = UserFun(Self),
329
server_hello(S, User, SSH,
330
proplists:get_value(timeout, Opts,
333
NoDelay = proplists:get_value(tcp_nodelay, Opts, false),
334
ssh_tcp_wrap:server(Port, [{packet,line}, {active,once},
335
{ifaddr,Addr}, {reuseaddr,true},
340
%% Initialize basic ssh system
342
ssh_init(S, Role, Opts) ->
343
ssh_bits:install_messages(transport_messages()),
344
{A,B,C} = erlang:now(),
345
random:seed(A, B, C),
346
put(send_sequence, 0),
347
put(recv_sequence, 0),
350
Vsn = proplists:get_value(vsn, Opts, {2,0}),
351
Version = format_version(Vsn),
352
send_version(S, Version),
356
key_cb = proplists:get_value(key_cb, Opts, ssh_file),
357
io_cb = case proplists:get_value(user_interaction, Opts, true) of
363
Vsn = proplists:get_value(vsn, Opts, {1,99}),
364
Version = format_version(Vsn),
365
send_version(S, Version),
369
key_cb = proplists:get_value(key_cb, Opts, ssh_file),
370
io_cb = proplists:get_value(io_cb, Opts, ssh_io),
374
ssh_setopts(NewOpts, SSH) ->
376
SSH#ssh { opts = NewOpts ++ Opts }.
378
format_version({Major,Minor}) ->
379
"SSH-"++integer_to_list(Major)++"."++integer_to_list(Minor)++"-Erlang".
384
Random = ssh_bits:random(16),
385
Comp = case proplists:get_value(compression, SSH#ssh.opts, none) of
386
zlib -> ["zlib", "none"];
387
none -> ["none", "zlib"]
393
kex_algorithms = ["diffie-hellman-group1-sha1"],
394
server_host_key_algorithms = ["ssh-rsa", "ssh-dss"],
395
encryption_algorithms_client_to_server = ["3des-cbc"],
396
encryption_algorithms_server_to_client = ["3des-cbc"],
397
mac_algorithms_client_to_server = ["hmac-sha1"],
398
mac_algorithms_server_to_client = ["hmac-sha1"],
399
compression_algorithms_client_to_server = Comp,
400
compression_algorithms_server_to_client = Comp,
401
languages_client_to_server = [],
402
languages_server_to_client = []
407
kex_algorithms = ["diffie-hellman-group1-sha1"],
408
server_host_key_algorithms = ["ssh-dss"],
409
encryption_algorithms_client_to_server = ["3des-cbc"],
410
encryption_algorithms_server_to_client = ["3des-cbc"],
411
mac_algorithms_client_to_server = ["hmac-sha1"],
412
mac_algorithms_server_to_client = ["hmac-sha1"],
413
compression_algorithms_client_to_server = Comp,
414
compression_algorithms_server_to_client = Comp,
415
languages_client_to_server = [],
416
languages_server_to_client = []
421
server_hello(S, User, SSH, Timeout) ->
423
{tcp, S, V = "SSH-"++_} ->
424
Version = trim_tail(V),
425
?dbg(true, "client version: ~p\n",[Version]),
426
case string:tokens(Version, "-") of
428
negotiate(S, User, SSH#ssh { c_vsn = {2,0},
429
c_version = Version}, false);
431
negotiate(S, User, SSH#ssh { c_vsn = {2,0},
432
c_version = Version}, false);
434
negotiate(S, User, SSH#ssh { c_vsn = {1,3},
435
c_version = Version}, false);
437
negotiate(S, User, SSH#ssh { c_vsn = {1,5},
438
c_version = Version}, false);
440
exit(unknown_version)
443
?dbg(true, "info: ~p\n", [_Line]),
444
inet:setopts(S, [{active, once}]),
445
server_hello(S, User, SSH, Timeout)
447
?dbg(true, "server_hello timeout ~p\n", [Timeout]),
452
client_hello(S, User, SSH, Timeout) ->
454
{tcp, S, V = "SSH-"++_} ->
455
Version = trim_tail(V),
456
?dbg(true, "server version: ~p\n",[Version]),
457
case string:tokens(Version, "-") of
459
negotiate(S, User, SSH#ssh { s_vsn = {2,0},
460
s_version = Version }, true);
461
[_, "1.99" | _] -> %% compatible server
462
negotiate(S, User, SSH#ssh { s_vsn = {2,0},
463
s_version = Version }, true);
465
negotiate(S, User, SSH#ssh { s_vsn = {1,3},
466
s_version = Version }, true);
468
negotiate(S, User, SSH#ssh { s_vsn = {1,5},
469
s_version = Version }, true);
471
exit(unknown_version)
474
?dbg(true, "info: ~p\n", [_Line]),
475
inet:setopts(S, [{active, once}]),
476
client_hello(S, User, SSH, Timeout);
477
{tcp_error, S, Reason} ->
478
?dbg(true, "client_hello got tcp error ~p\n", [Reason]),
479
{error, {tcp_error, Reason}};
481
?dbg(true, "client_hello: tcp_closed\n", []),
484
io:format("Other ~p\n", [Other])
486
?dbg(true, "client_hello timeout ~p\n", [Timeout]),
491
%% Determine the version and algorithms
492
negotiate(S, User, SSH, UserAck) ->
493
inet:setopts(S, [{packet,0},{mode,binary}]),
494
send_negotiate(S, User, SSH, UserAck).
496
%% We start re-negotiate
497
send_negotiate(S, User, SSH, UserAck) ->
498
SendAlg = kex_init(SSH),
499
{ok, SendPdu} = send_algorithms(S, SSH, SendAlg),
500
{ok, {RecvPdu,RecvAlg}} = recv_algorithms(S, SSH),
501
kex_negotiate(S, User, SSH, UserAck, SendAlg, SendPdu, RecvAlg, RecvPdu).
503
%% Other side started re-negotiate
504
recv_negotiate(S, User, SSH, RecvAlg, UserAck) ->
505
RecvPdu = ssh_bits:encode(RecvAlg),
506
SendAlg = kex_init(SSH),
507
{ok, SendPdu} = send_algorithms(S, SSH, SendAlg),
508
send_msg(S, SSH, SendAlg),
509
kex_negotiate(S, User, SSH, UserAck, SendAlg, SendPdu, RecvAlg, RecvPdu).
512
kex_negotiate(S, User, SSH, UserAck, SendAlg, SendPdu, RecvAlg, RecvPdu) ->
515
SSH1 = SSH#ssh { c_keyinit = SendPdu, s_keyinit = RecvPdu },
516
case select_algorithm(SSH1, #alg {}, SendAlg, RecvAlg) of
518
ALG = SSH2#ssh.algorithms,
519
case client_kex(S, SSH2, ALG#alg.kex) of
521
newkeys(S, User, SSH3, UserAck);
523
kexfailed(S, User, UserAck, Error)
526
kexfailed(S, User, UserAck, Error)
530
SSH1 = SSH#ssh { c_keyinit = RecvPdu, s_keyinit = SendPdu },
531
case select_algorithm(SSH1, #alg {}, RecvAlg, SendAlg) of
533
ALG = SSH2#ssh.algorithms,
534
case server_kex(S, SSH2, ALG#alg.kex) of
536
newkeys(S, User, SSH3, UserAck);
538
kexfailed(S, User, UserAck, Error)
541
kexfailed(S, User, UserAck, Error)
545
newkeys(S, User, SSH, UserAck) ->
546
%% Send new keys and wait for newkeys
547
send_msg(S, SSH, #ssh_msg_newkeys {}),
548
case recv_msg(S, SSH) of
549
{ok, M} when record(M, ssh_msg_newkeys) ->
550
SSH1 = install_alg(SSH),
551
if UserAck == true ->
552
User ! {self(), {ok, self()}},
553
inet:setopts(S, [{active, once}]),
554
ssh_main(S, User, SSH1);
556
inet:setopts(S, [{active, once}]),
557
ssh_main(S, User, SSH1)
560
{error, bad_message};
567
client_kex(S, SSH, 'diffie-hellman-group1-sha1') ->
568
ssh_bits:install_messages(kexdh_messages()),
570
{Private, Public} = dh_gen_key(G,P,1024),
571
?dbg(?DBG_KEX, "public: ~.16B\n", [Public]),
572
send_msg(S, SSH, #ssh_msg_kexdh_init { e = Public }),
573
case recv_msg(S, SSH) of
574
{ok, R} when record(R, ssh_msg_kexdh_reply) ->
575
K_S = R#ssh_msg_kexdh_reply.public_host_key,
576
F = R#ssh_msg_kexdh_reply.f,
577
K = ssh_math:ipow(F, Private, P),
578
H = kex_h(SSH, K_S, Public, F, K),
579
H_SIG = R#ssh_msg_kexdh_reply.h_sig,
580
?dbg(?DBG_KEX, "shared_secret: ~s\n", [fmt_binary(K, 16, 4)]),
581
?dbg(?DBG_KEX, "hash: ~s\n", [fmt_binary(H, 16, 4)]),
582
case verify_host_key(S, SSH, K_S, H, H_SIG) of
584
{ok, SSH#ssh { shared_secret = K,
591
{error, bad_message};
595
client_kex(S, SSH, 'diffie-hellman-group-exchange-sha1') ->
596
ssh_bits:install_messages(kex_dh_gex_messages()),
600
send_msg(S, SSH, #ssh_msg_kex_dh_gex_request { min = Min,
603
case recv_msg(S, SSH) of
604
{ok, RG} when record(RG, ssh_msg_kex_dh_gex_group) ->
605
P = RG#ssh_msg_kex_dh_gex_group.p,
606
G = RG#ssh_msg_kex_dh_gex_group.g,
607
{Private, Public} = dh_gen_key(G,P, 1024),
608
?dbg(?DBG_KEX, "public: ~.16B\n", [Public]),
609
send_msg(S, SSH, #ssh_msg_kex_dh_gex_init { e = Public }),
610
case recv_msg(S, SSH) of
611
{ok, R} when record(R, ssh_msg_kex_dh_gex_reply) ->
612
K_S = R#ssh_msg_kex_dh_gex_reply.public_host_key,
613
F = R#ssh_msg_kex_dh_gex_reply.f,
614
K = ssh_math:ipow(F, Private, P),
615
H = kex_h(SSH, K_S, Min, NBits, Max, P, G, Public, F, K),
616
H_SIG = R#ssh_msg_kex_dh_gex_reply.h_sig,
617
?dbg(?DBG_KEX, "shared_secret: ~s\n",
618
[fmt_binary(K, 16, 4)]),
619
?dbg(?DBG_KEX, "hash: ~s\n",
620
[fmt_binary(H, 16, 4)]),
621
case verify_host_key(S, SSH, K_S, H, H_SIG) of
623
{ok, SSH#ssh { shared_secret = K,
630
{error, bad_message};
635
{error, bad_message};
639
client_kex(_S, _SSH, Kex) ->
640
{error, {bad_kex_algorithm, Kex}}.
643
server_kex(S, SSH, 'diffie-hellman-group1-sha1') ->
644
ssh_bits:install_messages(kexdh_messages()),
646
{Private, Public} = dh_gen_key(G,P,1024),
647
?dbg(?DBG_KEX, "public: ~.16B\n", [Public]),
648
case recv_msg(S, SSH) of
649
{ok, R} when record(R, ssh_msg_kexdh_init) ->
650
E = R#ssh_msg_kexdh_init.e,
651
K = ssh_math:ipow(E, Private, P),
652
{Key,K_S} = get_host_key(SSH),
653
H = kex_h(SSH, K_S, E, Public, K),
654
H_SIG = sign_host_key(S, SSH, Key, H),
656
#ssh_msg_kexdh_reply { public_host_key = K_S,
660
?dbg(?DBG_KEX, "shared_secret: ~s\n", [fmt_binary(K, 16, 4)]),
661
?dbg(?DBG_KEX, "hash: ~s\n", [fmt_binary(H, 16, 4)]),
662
{ok, SSH#ssh { shared_secret = K,
666
{error, bad_message};
670
server_kex(S, SSH, 'diffie-hellman-group-exchange-sha1') ->
671
ssh_bits:install_messages(kex_dh_gex_messages()),
672
R0 = recv_msg(S, SSH),
673
#ssh_msg_kex_dh_gex_request { min = Min,
676
{G,P} = dh_group1(), %% FIX ME!!!
677
send_msg(S, SSH, #ssh_msg_kex_dh_gex_group { p = P, g = G }),
678
{Private, Public} = dh_gen_key(G,P,1024),
679
?dbg(?DBG_KEX, "public: ~.16B\n", [Public]),
680
case recv_msg(S, SSH) of
681
{ok, R} when record(R, ssh_msg_kex_dh_gex_init) ->
682
E = R#ssh_msg_kex_dh_gex_init.e,
683
K = ssh_math:ipow(E, Private, P),
684
{Key,K_S} = get_host_key(SSH),
685
H = kex_h(SSH, K_S, Min, NBits, Max, P, G, E, Public, K),
686
H_SIG = sign_host_key(S, SSH, Key, H),
688
#ssh_msg_kex_dh_gex_reply { public_host_key = K_S,
692
?dbg(?DBG_KEX, "shared_secret: ~s\n", [fmt_binary(K, 16, 4)]),
693
?dbg(?DBG_KEX, "hash: ~s\n", [fmt_binary(H, 16, 4)]),
694
{ok, SSH#ssh { shared_secret = K,
698
{error, bad_message};
702
server_kex(_S, _SSH, Kex) ->
703
{error, {bad_kex_algorithm, Kex}}.
705
ssh_main(S, User, SSH) ->
708
%% This is a lazy way of gettting events without block
709
?dbg(?DBG_PACKET, "UNRECEIVE: ~w BYTES\n", [size(Data)]),
710
gen_tcp:unrecv(S, Data),
711
case recv_msg(S, SSH) of
712
{ok, M} when record(M, ssh_msg_unimplemented) ->
713
?dbg(true, "UNIMPLEMENTED: ~p\n",
714
[M#ssh_msg_unimplemented.sequence]),
715
inet:setopts(S, [{active, once}]),
716
ssh_main(S, User, SSH);
717
{ok,M} when record(M, ssh_msg_disconnect) ->
718
User ! {ssh_msg, self(), M},
719
?dbg(true, "DISCONNECT: ~w ~s\n",
720
[M#ssh_msg_disconnect.code,
721
M#ssh_msg_disconnect.description]),
724
{ok,M} when record(M, ssh_msg_kexinit) ->
725
recv_negotiate(S, User, SSH, M, false);
728
User ! {ssh_msg, self(), M},
729
inet:setopts(S, [{active, once}]),
730
ssh_main(S, User, SSH);
731
{error, unimplemented} ->
733
#ssh_msg_unimplemented { sequence =
734
get(recv_sequence)-1}),
735
inet:setopts(S, [{active, once}]),
736
ssh_main(S, User, SSH);
738
inet:setopts(S, [{active, once}]),
740
ssh_main(S, User, SSH)
744
User ! {ssh_msg, self(),
745
#ssh_msg_disconnect { code=?SSH_DISCONNECT_CONNECTION_LOST,
746
description = "Connection closed",
748
gen_tcp:close(S), %% CHECK ME, is this needed ?
751
{ssh_msg, User, Msg} ->
752
send_msg(S, SSH, Msg),
753
if record(Msg, ssh_msg_disconnect) ->
756
ssh_main(S, User, SSH)
759
{ssh_install, Table} ->
760
ssh_bits:install_messages(Table),
761
ssh_main(S, User, SSH);
763
{ssh_uninstall, Table} ->
764
ssh_bits:uninstall_messages(Table),
765
ssh_main(S, User, SSH);
767
{ssh_renegotiate, UserAck, Opts} ->
768
%% Of some reason, the socket is still active, once when we
769
%% get here, which yelds EINVAL when doing recv. This might be a bug...
770
inet:setopts(S, [{active, false}]),
771
send_negotiate(S, User, ssh_setopts(Opts, SSH), UserAck);
773
{ssh_call, From, close} ->
774
?dbg(true, "Call: close from ~p\n", [From]),
779
{ssh_call, From, peername} ->
780
P = inet:peername(S),
781
io:format("peername~p\n", [P]),
783
ssh_main(S, User, SSH);
785
{ssh_call, From, Req} ->
786
?dbg(true, "Call: ~p from ~p\n", [Req,From]),
787
SSH1 = handle_call(Req, From, SSH),
788
ssh_main(S, User, SSH1);
791
?dbg(true, "ssh_loop: got ~p\n", [_Other]),
792
ssh_main(S, User, SSH)
796
%% Handle call's to ssh_transport
798
handle_call({get_cb,io}, From, SSH) ->
799
reply(From, {ok, SSH#ssh.io_cb}),
801
handle_call({get_cb,key}, From, SSH) ->
802
reply(From, {ok, SSH#ssh.key_cb}),
804
handle_call(get_session_id, From, SSH) ->
805
reply(From, {ok, SSH#ssh.session_id}),
807
handle_call(_Other, From, SSH) ->
808
reply(From, {error, bad_call}),
811
reply([Pid|Ref], Reply) ->
812
?dbg(true, "Reply: ~p\n", [Reply]),
817
%% The host key should be read from storage
820
#ssh{key_cb = Mod, opts = Opts, algorithms = ALG} = SSH,
821
Scope = proplists:get_value(key_scope, Opts, system),
824
case Mod:private_host_rsa_key(Scope, Opts) of
825
{ok,Key=#ssh_key { public={N,E}} } ->
826
?dbg(true, "x~n", []),
828
ssh_bits:encode(["ssh-rsa",E,N],[string,mpint,mpint])};
830
?dbg(true, "y~n", []),
834
case Mod:private_host_dsa_key(Scope, Opts) of
835
{ok,Key=#ssh_key { public={P,Q,G,Y}}} ->
836
{Key, ssh_bits:encode(["ssh-dss",P,Q,G,Y],
837
[string,mpint,mpint,mpint,mpint])};
842
exit({error, bad_key_type})
845
sign_host_key(_S, SSH, Private, H) ->
846
ALG = SSH#ssh.algorithms,
847
Module = case ALG#alg.hkey of
848
'ssh-rsa' -> ssh_rsa;
849
'ssh-dss' -> ssh_dsa;
852
case catch Module:sign(Private, H) of
854
error_logger:format("SIGN FAILED: ~p\n", [Reason]),
857
ssh_bits:encode([Module:alg_name() ,SIG],[string,binary])
860
verify_host_key(_S, SSH, K_S, H, H_SIG) ->
861
ALG = SSH#ssh.algorithms,
864
case ssh_bits:decode(K_S,[string,mpint,mpint]) of
866
["ssh-rsa",SIG] = ssh_bits:decode(H_SIG,[string,binary]),
867
Public = #ssh_key { type=rsa, public={N,E} },
868
case catch ssh_rsa:verify(Public, H, SIG) of
870
error_logger:format("VERIFY FAILED: ~p\n", [Reason]),
871
{error, bad_signature};
873
known_host_key(SSH, Public, "ssh-rsa")
879
case ssh_bits:decode(K_S,[string,mpint,mpint,mpint,mpint]) of
880
["ssh-dss",P,Q,G,Y] ->
881
["ssh-dss",SIG] = ssh_bits:decode(H_SIG,[string,binary]),
882
Public = #ssh_key { type=dsa, public={P,Q,G,Y} },
883
case catch ssh_dsa:verify(Public, H, SIG) of
885
error_logger:format("VERIFY FAILED: ~p\n", [Reason]),
886
{error, bad_signature};
888
known_host_key(SSH, Public, "ssh-dss")
891
{error, bad_host_key_format}
894
{error, bad_host_key_algorithm}
897
accepted_host(SSH, Peer, Opts) ->
898
case proplists:get_value(silently_accept_hosts, Opts, false) of
902
yes_no(SSH, "New host "++Peer++" accept")
905
known_host_key(SSH, Public, Alg) ->
906
#ssh{opts = Opts, key_cb = Mod, peer = Peer} = SSH,
907
case Mod:lookup_host_key(Peer, Alg, Opts) of
911
error_logger:format("known_host_key: Public ~p BadPublic ~p\n", [Public, BadPublic]),
912
{error, bad_public_key};
913
{error, not_found} ->
914
case accepted_host(SSH, Peer, Opts) of
916
Mod:add_host_key(Peer, Public, Opts);
922
send_algorithms(S, SSH, KexInit) ->
923
Payload = ssh_bits:encode(KexInit),
924
?dbg(?DBG_MESSAGE, "SEND_MSG: ~70p\n", [KexInit]),
925
Res = send_packet(S, SSH, Payload),
929
recv_algorithms(S, SSH) ->
930
case recv_packet(S, SSH) of
932
case ssh_bits:decode(Packet) of
934
?dbg(?DBG_MESSAGE, "RECV_MSG: ~70p\n", [R]),
940
?dbg(?DBG_MESSAGE, "RECV_MSG: ~p\n", [Error]),
944
%% Each of the algorithm strings MUST be a comma-separated list of
945
%% algorithm names (see ''Algorithm Naming'' in [SSH-ARCH]). Each
946
%% supported (allowed) algorithm MUST be listed in order of preference.
948
%% The first algorithm in each list MUST be the preferred (guessed)
949
%% algorithm. Each string MUST contain at least one algorithm name.
951
select_algorithm(SSH, ALG, C, S) ->
952
%% find out the selected algorithm
953
C_Enc = select(C#ssh_msg_kexinit.encryption_algorithms_client_to_server,
954
S#ssh_msg_kexinit.encryption_algorithms_client_to_server),
956
C_Mac = select(C#ssh_msg_kexinit.mac_algorithms_client_to_server,
957
S#ssh_msg_kexinit.mac_algorithms_client_to_server),
959
C_Cmp = select(C#ssh_msg_kexinit.compression_algorithms_client_to_server,
960
S#ssh_msg_kexinit.compression_algorithms_client_to_server),
962
C_Lng = select(C#ssh_msg_kexinit.languages_client_to_server,
963
S#ssh_msg_kexinit.languages_client_to_server),
965
S_Enc = select(C#ssh_msg_kexinit.encryption_algorithms_server_to_client,
966
S#ssh_msg_kexinit.encryption_algorithms_server_to_client),
968
S_Mac = select(C#ssh_msg_kexinit.mac_algorithms_server_to_client,
969
S#ssh_msg_kexinit.mac_algorithms_server_to_client),
971
S_Cmp = select(C#ssh_msg_kexinit.compression_algorithms_server_to_client,
972
S#ssh_msg_kexinit.compression_algorithms_server_to_client),
974
S_Lng = select(C#ssh_msg_kexinit.languages_server_to_client,
975
S#ssh_msg_kexinit.languages_server_to_client),
977
HKey = select_all(C#ssh_msg_kexinit.server_host_key_algorithms,
978
S#ssh_msg_kexinit.server_host_key_algorithms),
983
%% Fixme verify Kex against HKey list and algorithms
985
Kex = select(C#ssh_msg_kexinit.kex_algorithms,
986
S#ssh_msg_kexinit.kex_algorithms),
988
ALG1 = ALG#alg { kex = Kex, hkey = HK },
990
ALG2 = save_alg(SSH#ssh.role,
1000
{ok, SSH#ssh { algorithms = ALG2 }}.
1003
save_alg(Role, ALG, [{Key,A} | As]) ->
1004
if A == undefined ->
1005
save_alg(Role, ALG, As);
1011
save_alg(Role,ALG#alg { encrypt = A }, As);
1013
save_alg(Role,ALG#alg { decrypt = A }, As)
1019
save_alg(Role,ALG#alg { encrypt = A }, As);
1021
save_alg(Role,ALG#alg { decrypt = A }, As)
1027
save_alg(Role,ALG#alg { send_mac=A }, As);
1029
save_alg(Role,ALG#alg { recv_mac=A }, As)
1035
save_alg(Role,ALG#alg { send_mac = A }, As);
1037
save_alg(Role,ALG#alg { recv_mac = A }, As)
1043
save_alg(Role,ALG#alg { compress = A }, As);
1045
save_alg(Role,ALG#alg { decompress = A }, As)
1051
save_alg(Role, ALG#alg { compress = A }, As);
1053
save_alg(Role, ALG#alg { decompress = A }, As)
1055
c_lng -> save_alg(Role, ALG#alg { c_lng = A }, As);
1056
s_lng -> save_alg(Role, ALG#alg { s_lng = A }, As)
1059
save_alg(_Role, ALG, []) ->
1063
SSH1 = alg_final(SSH),
1064
SSH2 = alg_setup(SSH1),
1068
ALG = SSH#ssh.algorithms,
1069
?dbg(?DBG_ALG, "ALG: setup ~p\n", [ALG]),
1070
SSH#ssh { kex = ALG#alg.kex,
1071
hkey = ALG#alg.hkey,
1072
encrypt = ALG#alg.encrypt,
1073
decrypt = ALG#alg.decrypt,
1074
send_mac = ALG#alg.send_mac,
1075
send_mac_size = mac_digest_size(ALG#alg.send_mac),
1076
recv_mac = ALG#alg.recv_mac,
1077
recv_mac_size = mac_digest_size(ALG#alg.recv_mac),
1078
compress = ALG#alg.compress,
1079
decompress = ALG#alg.decompress,
1080
c_lng = ALG#alg.c_lng,
1081
s_lng = ALG#alg.s_lng,
1082
algorithms = undefined
1086
?dbg(?DBG_ALG, "ALG: init\n", []),
1087
{ok,SSH1} = send_mac_init(SSH0),
1088
{ok,SSH2} = recv_mac_init(SSH1),
1089
{ok,SSH3} = encrypt_init(SSH2),
1090
{ok,SSH4} = decrypt_init(SSH3),
1091
{ok,SSH5} = compress_init(SSH4),
1092
{ok,SSH6} = decompress_init(SSH5),
1096
?dbg(?DBG_ALG, "ALG: final\n", []),
1097
{ok,SSH1} = send_mac_final(SSH0),
1098
{ok,SSH2} = recv_mac_final(SSH1),
1099
{ok,SSH3} = encrypt_final(SSH2),
1100
{ok,SSH4} = decrypt_final(SSH3),
1101
{ok,SSH5} = compress_final(SSH4),
1102
{ok,SSH6} = decompress_final(SSH5),
1107
select_all(CL, SL) ->
1108
A = CL -- SL, %% algortihms only used by client
1109
%% algorithms used by client and server (client pref)
1110
map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A)).
1115
C = case select_all(CL,SL) of
1119
?dbg(?DBG_ALG, "ALG: select: ~p ~p = ~p\n", [CL, SL, C]),
1122
send_version(S, Version) ->
1123
gen_tcp:send(S, [Version,"\r\n"]).
1126
send_msg(S, SSH, Record) ->
1127
?dbg(?DBG_MESSAGE, "SEND_MSG: ~70p\n", [Record]),
1128
Bin = ssh_bits:encode(Record),
1129
?dbg(?DBG_BIN_MESSAGE, "Encoded: ~70p\n", [Bin]),
1130
send_packet(S, SSH, Bin).
1134
%% TotalLen = 4 + 1 + size(Data) + size(Padding)
1135
%% PaddingLen = TotalLen - (size(Data)+4+1)
1137
send_packet(S, SSH, Data0) when binary(Data0) ->
1138
Data = compress(SSH, Data0),
1139
BlockSize = SSH#ssh.encrypt_block_size,
1140
PL = (BlockSize - ((4 + 1 + size(Data)) rem BlockSize)) rem BlockSize,
1141
PaddingLen = if PL < 4 -> PL+BlockSize;
1144
Padding = ssh_bits:random(PaddingLen),
1145
PacketLen = 1 + PaddingLen + size(Data),
1146
Packet = <<?UINT32(PacketLen),?BYTE(PaddingLen),
1147
Data/binary, Padding/binary>>,
1148
EncPacket = encrypt(SSH, Packet),
1149
Seq = get(send_sequence),
1150
MAC = send_mac(SSH, Packet, Seq),
1151
?dbg(?DBG_PACKET, "SEND_PACKET:~w len=~p,payload=~p,padding=~p,mac=~p\n",
1152
[Seq, PacketLen, size(Data), PaddingLen, MAC]),
1153
Res = gen_tcp:send(S, [EncPacket, MAC]),
1154
put(send_sequence, (Seq+1) band 16#ffffffff),
1158
case recv_packet(S, SSH) of
1160
case ssh_bits:decode(Packet) of
1161
{ok, M} when record(M, ssh_msg_debug) ->
1162
if M#ssh_msg_debug.always_display == true ->
1163
io:format("DEBUG: ~p\n",
1164
[M#ssh_msg_debug.message]);
1166
?dbg(true, "DEBUG: ~p\n",
1167
[M#ssh_msg_debug.message])
1169
inet:setopts(S, [{active, once}]),
1171
{ok, M} when record(M, ssh_msg_ignore) ->
1172
inet:setopts(S, [{active, once}]),
1175
?dbg(?DBG_MESSAGE, "RECV_MSG: ~70p\n", [Msg]),
1178
%% Fixme (send disconnect...)
1182
?dbg(?DBG_MESSAGE, "RECV_MSG: ~70p\n", [Error]),
1186
%% receive ONE packet
1187
recv_packet(S, SSH) ->
1188
BlockSize = SSH#ssh.decrypt_block_size,
1189
case gen_tcp:recv(S, BlockSize) of
1191
Data0 = decrypt(SSH, EncData0),
1192
<<?UINT32(PacketLen), _/binary>> = Data0,
1193
if PacketLen < 5; PacketLen > ?SSH_MAX_PACKET_SIZE ->
1194
terminate(S, SSH, ?SSH_DISCONNECT_PROTOCOL_ERROR,
1195
"Bad packet length "++
1196
integer_to_list(PacketLen));
1198
case gen_tcp:recv(S, (PacketLen - BlockSize)+4) of
1200
Data1 = decrypt(SSH, EncData1),
1201
Data = <<Data0/binary, Data1/binary>>,
1202
recv_packet_data(S, SSH, PacketLen, Data);
1211
recv_packet_data(S, SSH, PacketLen, Data) ->
1212
Seq = get(recv_sequence),
1213
Res = valid_mac(SSH, S, Data, Seq),
1214
put(recv_sequence, (Seq+1) band 16#ffffffff),
1217
<<_:32, PaddingLen:8, _/binary>> = Data,
1218
PayloadLen = PacketLen - PaddingLen - 1,
1219
<<_:32, _:8, Payload:PayloadLen/binary,
1220
_:PaddingLen/binary>> = Data,
1222
"RECV_PACKET:~w, len=~p,payload=~w,padding=~w\n",
1223
[Seq,PacketLen,PayloadLen,PaddingLen]),
1224
{ok, decompress(SSH, Payload)};
1226
?dbg(?DBG_PACKET, "RECV_PACKET:~w, len=~p\n",
1228
terminate(S, SSH, ?SSH_DISCONNECT_MAC_ERROR,
1229
"Bad MAC #"++ integer_to_list(Seq))
1233
kexfailed(S, User, UserAck, Error) ->
1236
{error, bad_message} ->
1237
"key exchanged failed: bad message received";
1239
"key exchanged failed"
1241
M = #ssh_msg_disconnect { code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
1242
description = Description,
1244
if UserAck == true ->
1245
User ! {self(), Error};
1247
User ! {ssh_msg, self(), M}
1254
%% Send a disconnect message
1255
terminate(S, SSH, Code, Message) ->
1256
M = #ssh_msg_disconnect { code=Code,
1257
description=Message,
1259
send_msg(S, SSH, M),
1267
%% public key algorithms
1269
%% ssh-dss REQUIRED sign Raw DSS Key
1270
%% ssh-rsa RECOMMENDED sign Raw RSA Key
1271
%% x509v3-sign-rsa OPTIONAL sign X.509 certificates (RSA key)
1272
%% x509v3-sign-dss OPTIONAL sign X.509 certificates (DSS key)
1273
%% spki-sign-rsa OPTIONAL sign SPKI certificates (RSA key)
1274
%% spki-sign-dss OPTIONAL sign SPKI certificates (DSS key)
1275
%% pgp-sign-rsa OPTIONAL sign OpenPGP certificates (RSA key)
1276
%% pgp-sign-dss OPTIONAL sign OpenPGP certificates (DSS key)
1281
%% diffie-hellman-group1-sha1 REQUIRED
1288
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1290
%% context stored in dictionary as 'encrypt_ctx'
1294
%% 3des-cbc REQUIRED
1295
%% three-key 3DES in CBC mode
1296
%% blowfish-cbc OPTIONAL Blowfish in CBC mode
1297
%% twofish256-cbc OPTIONAL Twofish in CBC mode,
1299
%% twofish-cbc OPTIONAL alias for "twofish256-cbc" (this
1300
%% is being retained for
1301
%% historical reasons)
1302
%% twofish192-cbc OPTIONAL Twofish with 192-bit key
1303
%% twofish128-cbc OPTIONAL Twofish with 128-bit key
1304
%% aes256-cbc OPTIONAL AES in CBC mode,
1306
%% aes192-cbc OPTIONAL AES with 192-bit key
1307
%% aes128-cbc RECOMMENDED AES with 128-bit key
1308
%% serpent256-cbc OPTIONAL Serpent in CBC mode, with
1310
%% serpent192-cbc OPTIONAL Serpent with 192-bit key
1311
%% serpent128-cbc OPTIONAL Serpent with 128-bit key
1312
%% arcfour OPTIONAL the ARCFOUR stream cipher
1313
%% idea-cbc OPTIONAL IDEA in CBC mode
1314
%% cast128-cbc OPTIONAL CAST-128 in CBC mode
1315
%% none OPTIONAL no encryption; NOT RECOMMENDED
1317
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1319
encrypt_init(SSH) ->
1320
case SSH#ssh.encrypt of
1325
case SSH#ssh.role of
1327
{hash(SSH, "A", 64),
1328
hash(SSH, "C", 192)};
1330
{hash(SSH, "B", 64),
1331
hash(SSH, "D", 192)}
1333
<<K1:8/binary, K2:8/binary, K3:8/binary>> = KD,
1334
put(encrypt_ctx, IV),
1335
{ok,SSH#ssh { encrypt_keys = {K1,K2,K3},
1336
encrypt_block_size = 8 }};
1338
exit({bad_algorithm,SSH#ssh.encrypt})
1341
encrypt_final(SSH) ->
1343
{ok, SSH#ssh { encrypt = none,
1344
encrypt_keys = undefined,
1345
encrypt_block_size = 8
1349
encrypt(SSH, Data) ->
1350
case SSH#ssh.encrypt of
1354
{K1,K2,K3} = SSH#ssh.encrypt_keys,
1355
IV0 = get(encrypt_ctx),
1356
?dbg(?DBG_CRYPTO, "encrypt: IV=~p K1=~p, K2=~p, K3=~p\n",
1358
Enc = crypto:des3_cbc_encrypt(K1,K2,K3,IV0,Data),
1359
?dbg(?DBG_CRYPTO, "encrypt: ~p -> ~p\n", [Data, Enc]),
1360
%% Enc = list_to_binary(E0),
1361
IV = crypto:des_cbc_ivec(Enc),
1362
put(encrypt_ctx, IV),
1365
exit({bad_algorithm,SSH#ssh.encrypt})
1369
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1371
%% context stored in dictionary as 'decrypt_ctx'
1372
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1374
decrypt_init(SSH) ->
1375
case SSH#ssh.decrypt of
1379
{IV,KD} = case SSH#ssh.role of
1381
{hash(SSH, "B", 64),
1382
hash(SSH, "D", 192)};
1384
{hash(SSH, "A", 64),
1385
hash(SSH, "C", 192)}
1387
<<K1:8/binary, K2:8/binary, K3:8/binary>> = KD,
1388
put(decrypt_ctx, IV),
1389
{ok,SSH#ssh{ decrypt_keys = {K1,K2,K3},
1390
decrypt_block_size = 8 }};
1392
exit({bad_algorithm,SSH#ssh.decrypt})
1395
decrypt_final(SSH) ->
1397
{ok, SSH#ssh { decrypt = none,
1398
decrypt_keys = undefined,
1399
decrypt_block_size = 8 }}.
1401
decrypt(SSH, Data) ->
1402
case SSH#ssh.decrypt of
1406
{K1,K2,K3} = SSH#ssh.decrypt_keys,
1407
IV0 = get(decrypt_ctx),
1408
?dbg(?DBG_CRYPTO, "decrypt: IV=~p K1=~p, K2=~p, K3=~p\n",
1410
Dec = crypto:des3_cbc_decrypt(K1,K2,K3,IV0,Data),
1411
%% Enc = list_to_binary(E0),
1412
?dbg(?DBG_CRYPTO, "decrypt: ~p -> ~p\n", [Data, Dec]),
1413
IV = crypto:des_cbc_ivec(Data),
1414
put(decrypt_ctx, IV),
1417
exit({bad_algorithm,SSH#ssh.decrypt})
1421
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1423
%% context stored in dictionary as 'compress_ctx'
1425
%% none REQUIRED no compression
1426
%% zlib OPTIONAL ZLIB (LZ77) compression
1427
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1429
compress_init(SSH) ->
1430
compress_init(SSH, 1).
1431
compress_init(SSH, Level) ->
1432
Compress = SSH#ssh.compress,
1433
?dbg(?DBG_ZLIB, "compress_init: ~p Level ~p\n", [Compress, Level]),
1439
case zlib:deflateInit(Z, Level) of
1441
put(compress_ctx, Z),
1448
exit({bad_algorithm,SSH#ssh.compress})
1451
compress_final(SSH) ->
1452
case SSH#ssh.compress of
1456
zlib:close(get(compress_ctx)),
1457
erase(compress_ctx),
1458
{ok, SSH#ssh { compress = none }};
1460
exit({bad_algorithm,SSH#ssh.compress})
1463
compress(SSH, Data) ->
1464
case SSH#ssh.compress of
1468
Compressed = zlib:deflate(get(compress_ctx), Data, sync),
1469
?dbg(?DBG_ZLIB, "deflate: ~p -> ~p\n", [Data, Compressed]),
1470
list_to_binary(Compressed);
1472
exit({bad_algorithm,SSH#ssh.compress})
1476
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1478
%% context stored in dictionary as 'decompress_ctx'
1479
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1481
decompress_init(SSH) ->
1482
case SSH#ssh.decompress of
1487
case zlib:inflateInit(Z) of
1489
put(decompress_ctx, Z),
1496
exit({bad_algorithm,SSH#ssh.decompress})
1499
decompress_final(SSH) ->
1500
case SSH#ssh.decompress of
1504
zlib:close(get(decompress_ctx)),
1505
erase(decompress_ctx),
1506
{ok, SSH#ssh { decompress = none }};
1508
exit({bad_algorithm,SSH#ssh.decompress})
1511
decompress(SSH, Data) ->
1512
case SSH#ssh.decompress of
1516
Decompressed = zlib:inflate(get(decompress_ctx), Data),
1517
?dbg(?DBG_ZLIB, "inflate: ~p -> ~p\n", [Data, Decompressed]),
1518
list_to_binary(Decompressed);
1520
exit({bad_algorithm,SSH#ssh.decompress})
1526
%% hmac-sha1 REQUIRED HMAC-SHA1 (digest length = key
1528
%% hmac-sha1-96 RECOMMENDED first 96 bits of HMAC-SHA1 (digest
1529
%% length = 12, key length = 20)
1530
%% hmac-md5 OPTIONAL HMAC-MD5 (digest length = key
1532
%% hmac-md5-96 OPTIONAL first 96 bits of HMAC-MD5 (digest
1533
%% length = 12, key length = 16)
1534
%% none OPTIONAL no MAC; NOT RECOMMENDED
1537
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1540
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1542
send_mac_init(SSH) ->
1543
case SSH#ssh.role of
1545
Key = hash(SSH, "E", mac_key_size(SSH#ssh.send_mac)),
1546
{ok, SSH#ssh { send_mac_key = Key }};
1548
Key = hash(SSH, "F", mac_key_size(SSH#ssh.send_mac)),
1549
{ok, SSH#ssh { send_mac_key = Key }}
1552
send_mac_final(SSH) ->
1553
{ok, SSH#ssh { send_mac = none, send_mac_key = undefined }}.
1555
send_mac(SSH, Data, Seq) ->
1556
case SSH#ssh.send_mac of
1560
crypto:sha_mac(SSH#ssh.send_mac_key, [<<?UINT32(Seq)>>, Data]);
1562
crypto:sha_mac_96(SSH#ssh.send_mac_key, [<<?UINT32(Seq)>>, Data]);
1564
crypto:md5_mac(SSH#ssh.send_mac_key, [<<?UINT32(Seq)>>, Data]);
1566
crypto:md5_mac_96(SSH#ssh.send_mac_key, [<<?UINT32(Seq)>>, Data]);
1568
exit({bad_algorithm,SSH#ssh.send_mac})
1572
recv_mac_init(SSH) ->
1573
case SSH#ssh.role of
1575
Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)),
1576
{ok, SSH#ssh { recv_mac_key = Key }};
1578
Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)),
1579
{ok, SSH#ssh { recv_mac_key = Key }}
1582
recv_mac_final(SSH) ->
1583
{ok, SSH#ssh { recv_mac = none, recv_mac_key = undefined }}.
1585
recv_mac(SSH, Data, Seq) ->
1586
case SSH#ssh.recv_mac of
1590
crypto:sha_mac(SSH#ssh.recv_mac_key, [<<?UINT32(Seq)>>, Data]);
1592
crypto:sha_mac_96(SSH#ssh.recv_mac_key, [<<?UINT32(Seq)>>, Data]);
1594
crypto:md5_mac(SSH#ssh.recv_mac_key, [<<?UINT32(Seq)>>, Data]);
1596
crypto:md5_mac_96(SSH#ssh.recv_mac_key, [<<?UINT32(Seq)>>, Data]);
1598
exit({bad_algorithm,SSH#ssh.recv_mac})
1602
%% return N hash bytes (HASH)
1603
hash(SSH, Char, Bits) ->
1606
'diffie-hellman-group1-sha1' ->
1607
fun(Data) -> crypto:sha(Data) end;
1608
'diffie-hellman-group-exchange-sha1' ->
1609
fun(Data) -> crypto:sha(Data) end;
1611
exit({bad_algorithm,SSH#ssh.kex})
1613
hash(SSH, Char, Bits, HASH).
1615
hash(_SSH, _Char, 0, _HASH) ->
1617
hash(SSH, Char, N, HASH) ->
1618
K = ssh_bits:mpint(SSH#ssh.shared_secret),
1619
H = SSH#ssh.exchanged_hash,
1620
SessionID = SSH#ssh.session_id,
1621
K1 = HASH([K, H, Char, SessionID]),
1623
<<Key:Sz/binary, _/binary>> = hash(K, H, K1, N-128, HASH),
1624
?dbg(?DBG_KEX, "Key ~s: ~s\n", [Char, fmt_binary(Key, 16, 4)]),
1627
hash(_K, _H, Ki, N, _HASH) when N =< 0 ->
1629
hash(K, H, Ki, N, HASH) ->
1630
Kj = HASH([K, H, Ki]),
1631
hash(K, H, <<Ki/binary, Kj/binary>>, N-128, HASH).
1633
%% calcuation of H (diffie-hellman-group1-sha1)
1634
%% Must use ssh#ssh.algorithms here because new algorithms
1635
%% are not install at this point
1637
kex_h(SSH, K_S, E, F, K) ->
1638
L = ssh_bits:encode([SSH#ssh.c_version, SSH#ssh.s_version,
1639
SSH#ssh.c_keyinit, SSH#ssh.s_keyinit,
1641
[string,string,string,string,string,
1642
mpint,mpint,mpint]),
1645
kex_h(SSH, K_S, Min, NBits, Max, Prime, Gen, E, F, K) ->
1646
L = if Min==-1; Max==-1 ->
1647
Ts = [string,string,string,string,string,
1649
mpint,mpint,mpint,mpint,mpint],
1650
ssh_bits:encode([SSH#ssh.c_version,SSH#ssh.s_version,
1651
SSH#ssh.c_keyinit,SSH#ssh.s_keyinit,
1652
K_S, NBits, Prime, Gen, E,F,K],
1655
Ts = [string,string,string,string,string,
1656
uint32,uint32,uint32,
1657
mpint,mpint,mpint,mpint,mpint],
1658
ssh_bits:encode([SSH#ssh.c_version,SSH#ssh.s_version,
1659
SSH#ssh.c_keyinit,SSH#ssh.s_keyinit,
1660
K_S, Min, NBits, Max,
1661
Prime, Gen, E,F,K], Ts)
1668
mac_key_size('hmac-sha1') -> 20*8;
1669
mac_key_size('hmac-sha1-96') -> 20*8;
1670
mac_key_size('hmac-md5') -> 16*8;
1671
mac_key_size('hmac-md5-96') -> 16*8;
1672
mac_key_size(none) -> 0;
1673
mac_key_size(_) -> exit(bad_algoritm).
1675
mac_digest_size('hmac-sha1') -> 20;
1676
mac_digest_size('hmac-sha1-96') -> 12;
1677
mac_digest_size('hmac-md5') -> 20;
1678
mac_digest_size('hmac-md5-96') -> 12;
1679
mac_digest_size(none) -> 0;
1680
mac_digest_size(_) -> exit(bad_algoritm).
1682
%% integrity_char(send, client) -> "E";
1683
%% integrity_char(recv, server) -> "E";
1684
%% integrity_char(send, server) -> "F";
1685
%% integrity_char(recv, client) -> "F".
1687
valid_mac(SSH, S, Data, Seq) ->
1688
if SSH#ssh.recv_mac_size == 0 ->
1691
{ok,MAC0} = gen_tcp:recv(S, SSH#ssh.recv_mac_size),
1692
?dbg(?DBG_MAC, "~p: MAC0=~p\n", [Seq, MAC0]),
1693
MAC1 = recv_mac(SSH, Data, Seq),
1694
?dbg(?DBG_MAC, "~p: MAC1=~p\n", [Seq, MAC1]),
1698
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1700
%% Diffie-Hellman utils
1702
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1705
{2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF}.
1707
dh_gen_key(G,P, _Bits) ->
1708
Private = ssh_bits:irandom(ssh_bits:isize(P)-1, 1, 1),
1709
Public = ssh_math:ipow(G, Private, P),
1713
%% reverse(trim_head(reverse(trim_head(Str)))).
1716
reverse(trim_head(reverse(Str))).
1718
trim_head([$\s|Cs]) -> trim_head(Cs);
1719
trim_head([$\t|Cs]) -> trim_head(Cs);
1720
trim_head([$\n|Cs]) -> trim_head(Cs);
1721
trim_head([$\r|Cs]) -> trim_head(Cs);
1722
trim_head(Cs) -> Cs.
1726
%% Format integers and binaries as hex blocks
1730
%% fmt_binary(B, 0, 0).
1732
%% fmt_binary(B, BlockSize) ->
1733
%% fmt_binary(B, BlockSize, 0).
1735
fmt_binary(B, BlockSize, GroupSize) ->
1736
fmt_block(fmt_bin(B), BlockSize, GroupSize).
1738
fmt_block(Bin, BlockSize, GroupSize) ->
1739
fmt_block(Bin, BlockSize, 0, GroupSize).
1742
fmt_block(Bin, 0, _I, _G) ->
1743
binary_to_list(Bin);
1744
fmt_block(Bin, Sz, G, G) when G =/= 0 ->
1745
["\n" | fmt_block(Bin, Sz, 0, G)];
1746
fmt_block(Bin, Sz, I, G) ->
1748
<<Block:Sz/binary, Tail/binary>> ->
1750
[binary_to_list(Block)];
1752
[binary_to_list(Block), " " | fmt_block(Tail, Sz, I+1, G)]
1757
[binary_to_list(Bin)]
1760
%% Format integer or binary as hex
1761
fmt_bin(X) when integer(X) ->
1762
list_to_binary(io_lib:format("~.16B", [X]));
1763
fmt_bin(X) when binary(X) ->
1765
<<Y:Sz/unsigned-big>> = X,
1766
Fmt = "~"++integer_to_list(size(X)*2)++".16.0B",
1767
list_to_binary(io_lib:format(Fmt, [Y])).
1771
%% Retrieve session_id from ssh, needed by public-key auth
1772
get_session_id(SSH) ->
1773
{ok, SessionID} = call(SSH, get_session_id),