net-ssh 1.1.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +37 -0
- data/Manifest +101 -0
- data/README.rdoc +110 -0
- data/Rakefile +26 -0
- data/{THANKS → THANKS.rdoc} +2 -5
- data/lib/net/ssh.rb +189 -57
- data/lib/net/ssh/authentication/agent.rb +175 -0
- data/lib/net/ssh/authentication/constants.rb +18 -0
- data/lib/net/ssh/authentication/key_manager.rb +166 -0
- data/lib/net/ssh/authentication/methods/abstract.rb +60 -0
- data/lib/net/ssh/authentication/methods/hostbased.rb +71 -0
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +66 -0
- data/lib/net/ssh/authentication/methods/password.rb +39 -0
- data/lib/net/ssh/authentication/methods/publickey.rb +92 -0
- data/lib/net/ssh/authentication/pageant.rb +176 -0
- data/lib/net/ssh/authentication/session.rb +116 -0
- data/lib/net/ssh/buffer.rb +339 -0
- data/lib/net/ssh/buffered_io.rb +149 -0
- data/lib/net/ssh/config.rb +173 -0
- data/lib/net/ssh/connection/channel.rb +575 -454
- data/lib/net/ssh/connection/constants.rb +31 -45
- data/lib/net/ssh/connection/session.rb +569 -0
- data/lib/net/ssh/connection/term.rb +176 -88
- data/lib/net/ssh/errors.rb +83 -61
- data/lib/net/ssh/key_factory.rb +85 -0
- data/lib/net/ssh/known_hosts.rb +129 -0
- data/lib/net/ssh/loggable.rb +61 -0
- data/lib/net/ssh/packet.rb +102 -0
- data/lib/net/ssh/prompt.rb +93 -0
- data/lib/net/ssh/proxy/errors.rb +8 -28
- data/lib/net/ssh/proxy/http.rb +75 -107
- data/lib/net/ssh/proxy/socks4.rb +35 -48
- data/lib/net/ssh/proxy/socks5.rb +76 -108
- data/lib/net/ssh/service/forward.rb +267 -0
- data/lib/net/ssh/test.rb +89 -0
- data/lib/net/ssh/test/channel.rb +129 -0
- data/lib/net/ssh/test/extensions.rb +152 -0
- data/lib/net/ssh/test/kex.rb +44 -0
- data/lib/net/ssh/test/local_packet.rb +51 -0
- data/lib/net/ssh/test/packet.rb +81 -0
- data/lib/net/ssh/test/remote_packet.rb +38 -0
- data/lib/net/ssh/test/script.rb +157 -0
- data/lib/net/ssh/test/socket.rb +59 -0
- data/lib/net/ssh/transport/algorithms.rb +384 -0
- data/lib/net/ssh/transport/cipher_factory.rb +72 -0
- data/lib/net/ssh/transport/constants.rb +22 -58
- data/lib/net/ssh/transport/hmac.rb +31 -0
- data/lib/net/ssh/transport/hmac/abstract.rb +48 -0
- data/lib/net/ssh/transport/hmac/md5.rb +12 -0
- data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
- data/lib/net/ssh/transport/hmac/none.rb +15 -0
- data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
- data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
- data/lib/net/ssh/transport/identity_cipher.rb +40 -0
- data/lib/net/ssh/transport/kex.rb +13 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
- data/lib/net/ssh/{util → transport}/openssl.rb +22 -40
- data/lib/net/ssh/transport/packet_stream.rb +230 -0
- data/lib/net/ssh/transport/server_version.rb +61 -0
- data/lib/net/ssh/transport/session.rb +225 -303
- data/lib/net/ssh/transport/state.rb +170 -0
- data/lib/net/ssh/verifiers/lenient.rb +30 -0
- data/lib/net/ssh/verifiers/null.rb +12 -0
- data/lib/net/ssh/verifiers/strict.rb +53 -0
- data/lib/net/ssh/version.rb +57 -26
- data/net-ssh.gemspec +54 -0
- data/setup.rb +1585 -0
- data/test/authentication/methods/common.rb +28 -0
- data/test/authentication/methods/test_abstract.rb +51 -0
- data/test/authentication/methods/test_hostbased.rb +108 -0
- data/test/authentication/methods/test_keyboard_interactive.rb +98 -0
- data/test/authentication/methods/test_password.rb +50 -0
- data/test/authentication/methods/test_publickey.rb +123 -0
- data/test/authentication/test_agent.rb +205 -0
- data/test/authentication/test_key_manager.rb +100 -0
- data/test/authentication/test_session.rb +93 -0
- data/test/common.rb +106 -0
- data/test/configs/exact_match +8 -0
- data/test/configs/wild_cards +14 -0
- data/test/connection/test_channel.rb +452 -0
- data/test/connection/test_session.rb +483 -0
- data/test/test_all.rb +6 -0
- data/test/test_buffer.rb +336 -0
- data/test/test_buffered_io.rb +63 -0
- data/test/test_config.rb +78 -0
- data/test/test_key_factory.rb +67 -0
- data/test/transport/hmac/test_md5.rb +34 -0
- data/test/transport/hmac/test_md5_96.rb +25 -0
- data/test/transport/hmac/test_none.rb +34 -0
- data/test/transport/hmac/test_sha1.rb +34 -0
- data/test/transport/hmac/test_sha1_96.rb +25 -0
- data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
- data/test/transport/test_algorithms.rb +302 -0
- data/test/transport/test_cipher_factory.rb +163 -0
- data/test/transport/test_hmac.rb +34 -0
- data/test/transport/test_identity_cipher.rb +40 -0
- data/test/transport/test_packet_stream.rb +433 -0
- data/test/transport/test_server_version.rb +55 -0
- data/test/transport/test_session.rb +312 -0
- data/test/transport/test_state.rb +173 -0
- metadata +102 -253
- data/ChangeLog +0 -560
- data/LICENSE +0 -7
- data/NEWS +0 -152
- data/README +0 -14
- data/bin/rb-keygen +0 -210
- data/doc/LICENSE-BSD +0 -27
- data/doc/LICENSE-GPL +0 -280
- data/doc/LICENSE-RUBY +0 -56
- data/doc/manual-html/chapter-1.html +0 -388
- data/doc/manual-html/chapter-2.html +0 -552
- data/doc/manual-html/chapter-3.html +0 -470
- data/doc/manual-html/chapter-4.html +0 -413
- data/doc/manual-html/chapter-5.html +0 -525
- data/doc/manual-html/chapter-6.html +0 -456
- data/doc/manual-html/chapter-7.html +0 -343
- data/doc/manual-html/index.html +0 -235
- data/doc/manual-html/stylesheets/manual.css +0 -270
- data/doc/manual-html/stylesheets/ruby.css +0 -17
- data/doc/manual/chapter.erb +0 -38
- data/doc/manual/example.erb +0 -18
- data/doc/manual/index.erb +0 -29
- data/doc/manual/manual.rb +0 -311
- data/doc/manual/manual.yml +0 -73
- data/doc/manual/page.erb +0 -87
- data/doc/manual/parts/0000.txt +0 -5
- data/doc/manual/parts/0001.txt +0 -3
- data/doc/manual/parts/0002.txt +0 -40
- data/doc/manual/parts/0003.txt +0 -6
- data/doc/manual/parts/0004.txt +0 -7
- data/doc/manual/parts/0005.txt +0 -1
- data/doc/manual/parts/0006.txt +0 -49
- data/doc/manual/parts/0007.txt +0 -67
- data/doc/manual/parts/0008.txt +0 -43
- data/doc/manual/parts/0009.txt +0 -14
- data/doc/manual/parts/0010.txt +0 -7
- data/doc/manual/parts/0011.txt +0 -14
- data/doc/manual/parts/0012.txt +0 -3
- data/doc/manual/parts/0013.txt +0 -20
- data/doc/manual/parts/0014.txt +0 -32
- data/doc/manual/parts/0015.txt +0 -14
- data/doc/manual/parts/0016.txt +0 -28
- data/doc/manual/parts/0017.txt +0 -50
- data/doc/manual/parts/0018.txt +0 -35
- data/doc/manual/parts/0019.txt +0 -7
- data/doc/manual/parts/0020.txt +0 -72
- data/doc/manual/parts/0021.txt +0 -50
- data/doc/manual/parts/0022.txt +0 -42
- data/doc/manual/parts/0023.txt +0 -51
- data/doc/manual/parts/0024.txt +0 -18
- data/doc/manual/parts/0025.txt +0 -18
- data/doc/manual/parts/0026.txt +0 -15
- data/doc/manual/parts/0027.txt +0 -37
- data/doc/manual/parts/0028.txt +0 -16
- data/doc/manual/parts/0029.txt +0 -1
- data/doc/manual/parts/0030.txt +0 -52
- data/doc/manual/parts/0031.txt +0 -25
- data/doc/manual/stylesheets/manual.css +0 -270
- data/doc/manual/stylesheets/ruby.css +0 -17
- data/doc/manual/tutorial.erb +0 -30
- data/examples/auth-forward.rb +0 -41
- data/examples/channel-demo.rb +0 -81
- data/examples/port-forward.rb +0 -51
- data/examples/process-demo.rb +0 -91
- data/examples/remote-net-port-forward.rb +0 -45
- data/examples/remote-port-forward.rb +0 -80
- data/examples/shell-demo.rb +0 -46
- data/examples/ssh-client.rb +0 -67
- data/examples/sync-shell-demo.rb +0 -69
- data/examples/tail-demo.rb +0 -49
- data/lib/net/ssh/connection/driver.rb +0 -446
- data/lib/net/ssh/connection/services.rb +0 -72
- data/lib/net/ssh/host-key-verifier.rb +0 -52
- data/lib/net/ssh/known-hosts.rb +0 -96
- data/lib/net/ssh/lenient-host-key-verifier.rb +0 -25
- data/lib/net/ssh/null-host-key-verifier.rb +0 -14
- data/lib/net/ssh/service/agentforward/driver.rb +0 -78
- data/lib/net/ssh/service/agentforward/services.rb +0 -41
- data/lib/net/ssh/service/forward/driver.rb +0 -319
- data/lib/net/ssh/service/forward/local-network-handler.rb +0 -71
- data/lib/net/ssh/service/forward/remote-network-handler.rb +0 -83
- data/lib/net/ssh/service/forward/services.rb +0 -76
- data/lib/net/ssh/service/process/driver.rb +0 -153
- data/lib/net/ssh/service/process/open.rb +0 -193
- data/lib/net/ssh/service/process/popen3.rb +0 -178
- data/lib/net/ssh/service/process/services.rb +0 -66
- data/lib/net/ssh/service/services.rb +0 -60
- data/lib/net/ssh/service/shell/driver.rb +0 -86
- data/lib/net/ssh/service/shell/services.rb +0 -54
- data/lib/net/ssh/service/shell/shell.rb +0 -222
- data/lib/net/ssh/service/shell/sync.rb +0 -114
- data/lib/net/ssh/session.rb +0 -305
- data/lib/net/ssh/transport/algorithm-negotiator.rb +0 -275
- data/lib/net/ssh/transport/compress/compressor.rb +0 -53
- data/lib/net/ssh/transport/compress/decompressor.rb +0 -53
- data/lib/net/ssh/transport/compress/none-compressor.rb +0 -39
- data/lib/net/ssh/transport/compress/none-decompressor.rb +0 -39
- data/lib/net/ssh/transport/compress/services.rb +0 -68
- data/lib/net/ssh/transport/compress/zlib-compressor.rb +0 -60
- data/lib/net/ssh/transport/compress/zlib-decompressor.rb +0 -52
- data/lib/net/ssh/transport/errors.rb +0 -47
- data/lib/net/ssh/transport/identity-cipher.rb +0 -61
- data/lib/net/ssh/transport/kex/dh-gex.rb +0 -106
- data/lib/net/ssh/transport/kex/dh.rb +0 -249
- data/lib/net/ssh/transport/kex/services.rb +0 -62
- data/lib/net/ssh/transport/ossl/buffer-factory.rb +0 -52
- data/lib/net/ssh/transport/ossl/buffer.rb +0 -87
- data/lib/net/ssh/transport/ossl/cipher-factory.rb +0 -98
- data/lib/net/ssh/transport/ossl/digest-factory.rb +0 -51
- data/lib/net/ssh/transport/ossl/hmac-factory.rb +0 -71
- data/lib/net/ssh/transport/ossl/hmac/hmac.rb +0 -62
- data/lib/net/ssh/transport/ossl/hmac/md5-96.rb +0 -44
- data/lib/net/ssh/transport/ossl/hmac/md5.rb +0 -46
- data/lib/net/ssh/transport/ossl/hmac/none.rb +0 -46
- data/lib/net/ssh/transport/ossl/hmac/services.rb +0 -68
- data/lib/net/ssh/transport/ossl/hmac/sha1-96.rb +0 -44
- data/lib/net/ssh/transport/ossl/hmac/sha1.rb +0 -45
- data/lib/net/ssh/transport/ossl/key-factory.rb +0 -116
- data/lib/net/ssh/transport/ossl/services.rb +0 -149
- data/lib/net/ssh/transport/packet-stream.rb +0 -236
- data/lib/net/ssh/transport/services.rb +0 -146
- data/lib/net/ssh/transport/version-negotiator.rb +0 -73
- data/lib/net/ssh/userauth/agent.rb +0 -222
- data/lib/net/ssh/userauth/constants.rb +0 -35
- data/lib/net/ssh/userauth/driver.rb +0 -183
- data/lib/net/ssh/userauth/methods/hostbased.rb +0 -119
- data/lib/net/ssh/userauth/methods/keyboard-interactive.rb +0 -104
- data/lib/net/ssh/userauth/methods/password.rb +0 -70
- data/lib/net/ssh/userauth/methods/publickey.rb +0 -137
- data/lib/net/ssh/userauth/methods/services.rb +0 -90
- data/lib/net/ssh/userauth/pageant.rb +0 -197
- data/lib/net/ssh/userauth/services.rb +0 -141
- data/lib/net/ssh/userauth/userkeys.rb +0 -258
- data/lib/net/ssh/util/buffer.rb +0 -274
- data/lib/net/ssh/util/prompter.rb +0 -73
- data/test/ALL-TESTS.rb +0 -18
- data/test/connection/tc_channel.rb +0 -136
- data/test/connection/tc_driver.rb +0 -287
- data/test/connection/tc_integration.rb +0 -87
- data/test/proxy/tc_http.rb +0 -209
- data/test/proxy/tc_socks4.rb +0 -148
- data/test/proxy/tc_socks5.rb +0 -214
- data/test/service/agentforward/tc_driver.rb +0 -138
- data/test/service/forward/tc_driver.rb +0 -289
- data/test/service/forward/tc_local_network_handler.rb +0 -123
- data/test/service/forward/tc_remote_network_handler.rb +0 -111
- data/test/service/process/tc_driver.rb +0 -79
- data/test/service/process/tc_integration.rb +0 -119
- data/test/service/process/tc_open.rb +0 -179
- data/test/service/process/tc_popen3.rb +0 -164
- data/test/tc_integration.rb +0 -80
- data/test/transport/compress/tc_none_compress.rb +0 -41
- data/test/transport/compress/tc_none_decompress.rb +0 -45
- data/test/transport/compress/tc_zlib_compress.rb +0 -61
- data/test/transport/compress/tc_zlib_decompress.rb +0 -48
- data/test/transport/kex/tc_dh.rb +0 -312
- data/test/transport/kex/tc_dh_gex.rb +0 -71
- data/test/transport/ossl/fixtures/dsa-encrypted +0 -15
- data/test/transport/ossl/fixtures/dsa-encrypted-bad +0 -15
- data/test/transport/ossl/fixtures/dsa-unencrypted +0 -12
- data/test/transport/ossl/fixtures/dsa-unencrypted-bad +0 -12
- data/test/transport/ossl/fixtures/dsa-unencrypted.pub +0 -1
- data/test/transport/ossl/fixtures/not-a-private-key +0 -4
- data/test/transport/ossl/fixtures/not-supported +0 -2
- data/test/transport/ossl/fixtures/rsa-encrypted +0 -18
- data/test/transport/ossl/fixtures/rsa-encrypted-bad +0 -18
- data/test/transport/ossl/fixtures/rsa-unencrypted +0 -15
- data/test/transport/ossl/fixtures/rsa-unencrypted-bad +0 -15
- data/test/transport/ossl/fixtures/rsa-unencrypted.pub +0 -1
- data/test/transport/ossl/hmac/tc_hmac.rb +0 -58
- data/test/transport/ossl/hmac/tc_md5.rb +0 -50
- data/test/transport/ossl/hmac/tc_md5_96.rb +0 -50
- data/test/transport/ossl/hmac/tc_none.rb +0 -50
- data/test/transport/ossl/hmac/tc_sha1.rb +0 -50
- data/test/transport/ossl/hmac/tc_sha1_96.rb +0 -50
- data/test/transport/ossl/tc_buffer.rb +0 -97
- data/test/transport/ossl/tc_buffer_factory.rb +0 -67
- data/test/transport/ossl/tc_cipher_factory.rb +0 -84
- data/test/transport/ossl/tc_digest_factory.rb +0 -39
- data/test/transport/ossl/tc_hmac_factory.rb +0 -72
- data/test/transport/ossl/tc_key_factory.rb +0 -199
- data/test/transport/tc_algorithm_negotiator.rb +0 -170
- data/test/transport/tc_identity_cipher.rb +0 -52
- data/test/transport/tc_integration.rb +0 -115
- data/test/transport/tc_packet_stream.rb +0 -184
- data/test/transport/tc_session.rb +0 -296
- data/test/transport/tc_version_negotiator.rb +0 -86
- data/test/userauth/methods/tc_hostbased.rb +0 -136
- data/test/userauth/methods/tc_password.rb +0 -89
- data/test/userauth/methods/tc_publickey.rb +0 -167
- data/test/userauth/tc_agent.rb +0 -223
- data/test/userauth/tc_driver.rb +0 -190
- data/test/userauth/tc_integration.rb +0 -97
- data/test/userauth/tc_userkeys.rb +0 -265
- data/test/util/tc_buffer.rb +0 -217
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'net/ssh/connection/constants'
|
2
|
+
require 'net/ssh/transport/constants'
|
3
|
+
|
4
|
+
module Net; module SSH; module Test
|
5
|
+
|
6
|
+
# This is an abstract class, not to be instantiated directly, subclassed by
|
7
|
+
# Net::SSH::Test::LocalPacket and Net::SSH::Test::RemotePacket. It implements
|
8
|
+
# functionality common to those subclasses.
|
9
|
+
#
|
10
|
+
# These packets are not true packets, in that they don't represent what was
|
11
|
+
# actually sent between the hosst; rather, they represent what was expected
|
12
|
+
# to be sent, as dictated by the script (Net::SSH::Test::Script). Thus,
|
13
|
+
# though they are defined with data elements, these data elements are used
|
14
|
+
# to either validate data that was sent by the local host (Net::SSH::Test::LocalPacket)
|
15
|
+
# or to mimic the sending of data by the remote host (Net::SSH::Test::RemotePacket).
|
16
|
+
class Packet
|
17
|
+
include Net::SSH::Transport::Constants
|
18
|
+
include Net::SSH::Connection::Constants
|
19
|
+
|
20
|
+
# Ceate a new packet of the given +type+, and with +args+ being a list of
|
21
|
+
# data elements in the order expected for packets of the given +type+
|
22
|
+
# (see #types).
|
23
|
+
def initialize(type, *args)
|
24
|
+
@type = self.class.const_get(type.to_s.upcase)
|
25
|
+
@data = args
|
26
|
+
end
|
27
|
+
|
28
|
+
# The default for +remote?+ is false. Subclasses should override as necessary.
|
29
|
+
def remote?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
# The default for +local?+ is false. Subclasses should override as necessary.
|
34
|
+
def local?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
# Instantiates the packets data elements. When the packet was first defined,
|
39
|
+
# some elements may not have been fully realized, and were described as
|
40
|
+
# Proc objects rather than atomic types. This invokes those Proc objects
|
41
|
+
# and replaces them with their returned values. This allows for values
|
42
|
+
# like Net::SSH::Test::Channel#remote_id to be used in scripts before
|
43
|
+
# the remote_id is known (since it is only known after a channel has been
|
44
|
+
# confirmed open).
|
45
|
+
def instantiate!
|
46
|
+
@data.map! { |i| i.respond_to?(:call) ? i.call : i }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns an array of symbols describing the data elements for packets of
|
50
|
+
# the same type as this packet. These types are used to either validate
|
51
|
+
# sent packets (Net::SSH::Test::LocalPacket) or build received packets
|
52
|
+
# (Net::SSH::Test::RemotePacket).
|
53
|
+
#
|
54
|
+
# Not all packet types are defined here. As new packet types are required
|
55
|
+
# (e.g., a unit test needs to test that the remote host sent a packet that
|
56
|
+
# is not implemented here), the description of that packet should be
|
57
|
+
# added. Unsupported packet types will otherwise raise an exception.
|
58
|
+
def types
|
59
|
+
@types ||= case @type
|
60
|
+
when KEXINIT then
|
61
|
+
[:long, :long, :long, :long,
|
62
|
+
:string, :string, :string, :string, :string, :string, :string, :string, :string, :string,
|
63
|
+
:bool]
|
64
|
+
when NEWKEYS then []
|
65
|
+
when CHANNEL_OPEN then [:string, :long, :long, :long]
|
66
|
+
when CHANNEL_OPEN_CONFIRMATION then [:long, :long, :long, :long]
|
67
|
+
when CHANNEL_DATA then [:long, :string]
|
68
|
+
when CHANNEL_EOF, CHANNEL_CLOSE, CHANNEL_SUCCESS, CHANNEL_FAILURE then [:long]
|
69
|
+
when CHANNEL_REQUEST
|
70
|
+
parts = [:long, :string, :bool]
|
71
|
+
case @data[1]
|
72
|
+
when "exec", "subsystem" then parts << :string
|
73
|
+
when "exit-status" then parts << :long
|
74
|
+
else raise "don't know what to do about #{@data[1]} channel request"
|
75
|
+
end
|
76
|
+
else raise "don't know how to parse packet type #{@type}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end; end; end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'net/ssh/buffer'
|
2
|
+
require 'net/ssh/test/packet'
|
3
|
+
|
4
|
+
module Net; module SSH; module Test
|
5
|
+
|
6
|
+
# This is a specialization of Net::SSH::Test::Packet for representing mock
|
7
|
+
# packets that are received by the local (client) host. These are created
|
8
|
+
# automatically by Net::SSH::Test::Script and Net::SSH::Test::Channel by any
|
9
|
+
# of the gets_* methods.
|
10
|
+
class RemotePacket < Packet
|
11
|
+
# Returns +true+; this is a remote packet.
|
12
|
+
def remote?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
# The #process method should only be called on Net::SSH::Test::LocalPacket
|
17
|
+
# packets; if it is attempted on a remote packet, then it is an expectation
|
18
|
+
# mismatch (a remote packet was received when a local packet was expected
|
19
|
+
# to be sent). This will happen when either your test script
|
20
|
+
# (Net::SSH::Test::Script) or your program are wrong.
|
21
|
+
def process(packet)
|
22
|
+
raise "received packet type #{packet.read_byte} and was not expecting any packet"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns this remote packet as a string, suitable for parsing by
|
26
|
+
# Net::SSH::Transport::PacketStream and friends. When a remote packet is
|
27
|
+
# received, this method is called and the result concatenated onto the
|
28
|
+
# input buffer for the packet stream.
|
29
|
+
def to_s
|
30
|
+
@to_s ||= begin
|
31
|
+
instantiate!
|
32
|
+
string = Net::SSH::Buffer.from(:byte, @type, *types.zip(@data).flatten).to_s
|
33
|
+
[string.length, string].pack("NA*")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end; end; end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'net/ssh/test/channel'
|
2
|
+
require 'net/ssh/test/local_packet'
|
3
|
+
require 'net/ssh/test/remote_packet'
|
4
|
+
|
5
|
+
module Net; module SSH; module Test
|
6
|
+
|
7
|
+
# Represents a sequence of scripted events that identify the behavior that
|
8
|
+
# a test expects. Methods named "sends_*" create events for packets being
|
9
|
+
# sent from the local to the remote host, and methods named "gets_*" create
|
10
|
+
# events for packets being received by the local from the remote host.
|
11
|
+
#
|
12
|
+
# A reference to a script. is generally obtained in a unit test via the
|
13
|
+
# Net::SSH::Test#story helper method:
|
14
|
+
#
|
15
|
+
# story do |script|
|
16
|
+
# channel = script.opens_channel
|
17
|
+
# ...
|
18
|
+
# end
|
19
|
+
class Script
|
20
|
+
# The list of scripted events. These will be Net::SSH::Test::LocalPacket
|
21
|
+
# and Net::SSH::Test::RemotePacket instances.
|
22
|
+
attr_reader :events
|
23
|
+
|
24
|
+
# Create a new, empty script.
|
25
|
+
def initialize
|
26
|
+
@events = []
|
27
|
+
end
|
28
|
+
|
29
|
+
# Scripts the opening of a channel by adding a local packet sending the
|
30
|
+
# channel open request, and if +confirm+ is true (the default), also
|
31
|
+
# adding a remote packet confirming the new channel.
|
32
|
+
#
|
33
|
+
# A new Net::SSH::Test::Channel instance is returned, which can be used
|
34
|
+
# to script additional channel operations.
|
35
|
+
def opens_channel(confirm=true)
|
36
|
+
channel = Channel.new(self)
|
37
|
+
channel.remote_id = 5555
|
38
|
+
|
39
|
+
events << LocalPacket.new(:channel_open) { |p| channel.local_id = p[:remote_id] }
|
40
|
+
|
41
|
+
if confirm
|
42
|
+
events << RemotePacket.new(:channel_open_confirmation, channel.local_id, channel.remote_id, 0x20000, 0x10000)
|
43
|
+
end
|
44
|
+
|
45
|
+
channel
|
46
|
+
end
|
47
|
+
|
48
|
+
# A convenience method for adding an arbitrary local packet to the events
|
49
|
+
# list.
|
50
|
+
def sends(type, *args, &block)
|
51
|
+
events << LocalPacket.new(type, *args, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
# A convenience method for adding an arbitrary remote packet to the events
|
55
|
+
# list.
|
56
|
+
def gets(type, *args)
|
57
|
+
events << RemotePacket.new(type, *args)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Scripts the sending of a new channel request packet to the remote host.
|
61
|
+
# +channel+ should be an instance of Net::SSH::Test::Channel. +request+
|
62
|
+
# is a string naming the request type to send, +reply+ is a boolean
|
63
|
+
# indicating whether a response to this packet is required , and +data+
|
64
|
+
# is any additional request-specific data that this packet should send.
|
65
|
+
# +success+ indicates whether the response (if one is required) should be
|
66
|
+
# success or failure.
|
67
|
+
#
|
68
|
+
# If a reply is desired, a remote packet will also be queued, :channel_success
|
69
|
+
# if +success+ is true, or :channel_failure if +success+ is false.
|
70
|
+
#
|
71
|
+
# This will typically be called via Net::SSH::Test::Channel#sends_exec or
|
72
|
+
# Net::SSH::Test::Channel#sends_subsystem.
|
73
|
+
def sends_channel_request(channel, request, reply, data, success=true)
|
74
|
+
events << LocalPacket.new(:channel_request, channel.remote_id, request, reply, data)
|
75
|
+
if reply
|
76
|
+
if success
|
77
|
+
events << RemotePacket.new(:channel_success, channel.local_id)
|
78
|
+
else
|
79
|
+
events << RemotePacket.new(:channel_failure, channel.local_id)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Scripts the sending of a channel data packet. +channel+ must be a
|
85
|
+
# Net::SSH::Test::Channel object, and +data+ is the (string) data to
|
86
|
+
# expect will be sent.
|
87
|
+
#
|
88
|
+
# This will typically be called via Net::SSH::Test::Channel#sends_data.
|
89
|
+
def sends_channel_data(channel, data)
|
90
|
+
events << LocalPacket.new(:channel_data, channel.remote_id, data)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Scripts the sending of a channel EOF packet from the given
|
94
|
+
# Net::SSH::Test::Channel +channel+. This will typically be called via
|
95
|
+
# Net::SSH::Test::Channel#sends_eof.
|
96
|
+
def sends_channel_eof(channel)
|
97
|
+
events << LocalPacket.new(:channel_eof, channel.remote_id)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Scripts the sending of a channel close packet from the given
|
101
|
+
# Net::SSH::Test::Channel +channel+. This will typically be called via
|
102
|
+
# Net::SSH::Test::Channel#sends_close.
|
103
|
+
def sends_channel_close(channel)
|
104
|
+
events << LocalPacket.new(:channel_close, channel.remote_id)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Scripts the reception of a channel data packet from the remote host by
|
108
|
+
# the given Net::SSH::Test::Channel +channel+. This will typically be
|
109
|
+
# called via Net::SSH::Test::Channel#gets_data.
|
110
|
+
def gets_channel_data(channel, data)
|
111
|
+
events << RemotePacket.new(:channel_data, channel.local_id, data)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Scripts the reception of a channel request packet from the remote host by
|
115
|
+
# the given Net::SSH::Test::Channel +channel+. This will typically be
|
116
|
+
# called via Net::SSH::Test::Channel#gets_exit_status.
|
117
|
+
def gets_channel_request(channel, request, reply, data)
|
118
|
+
events << RemotePacket.new(:channel_request, channel.local_id, request, reply, data)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Scripts the reception of a channel EOF packet from the remote host by
|
122
|
+
# the given Net::SSH::Test::Channel +channel+. This will typically be
|
123
|
+
# called via Net::SSH::Test::Channel#gets_eof.
|
124
|
+
def gets_channel_eof(channel)
|
125
|
+
events << RemotePacket.new(:channel_eof, channel.local_id)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Scripts the reception of a channel close packet from the remote host by
|
129
|
+
# the given Net::SSH::Test::Channel +channel+. This will typically be
|
130
|
+
# called via Net::SSH::Test::Channel#gets_close.
|
131
|
+
def gets_channel_close(channel)
|
132
|
+
events << RemotePacket.new(:channel_close, channel.local_id)
|
133
|
+
end
|
134
|
+
|
135
|
+
# By default, removes the next event in the list and returns it. However,
|
136
|
+
# this can also be used to non-destructively peek at the next event in the
|
137
|
+
# list, by passing :first as the argument.
|
138
|
+
#
|
139
|
+
# # remove the next event and return it
|
140
|
+
# event = script.next
|
141
|
+
#
|
142
|
+
# # peek at the next event
|
143
|
+
# event = script.next(:first)
|
144
|
+
def next(mode=:shift)
|
145
|
+
events.send(mode)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Compare the given packet against the next event in the list. If there is
|
149
|
+
# no next event, an exception will be raised. This is called by
|
150
|
+
# Net::SSH::Test::Extensions::PacketStream#test_enqueue_packet.
|
151
|
+
def process(packet)
|
152
|
+
event = events.shift or raise "end of script reached, but got a packet type #{packet.read_byte}"
|
153
|
+
event.process(packet)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end; end; end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'stringio'
|
3
|
+
require 'net/ssh/test/extensions'
|
4
|
+
require 'net/ssh/test/script'
|
5
|
+
|
6
|
+
module Net; module SSH; module Test
|
7
|
+
|
8
|
+
# A mock socket implementation for use in testing. It implements the minimum
|
9
|
+
# necessary interface for interacting with the rest of the Net::SSH::Test
|
10
|
+
# system.
|
11
|
+
class Socket < StringIO
|
12
|
+
attr_reader :host, :port
|
13
|
+
|
14
|
+
# The Net::SSH::Test::Script object in use by this socket. This is the
|
15
|
+
# canonical script instance that should be used for any test depending on
|
16
|
+
# this socket instance.
|
17
|
+
attr_reader :script
|
18
|
+
|
19
|
+
# Create a new test socket. This will also instantiate a new Net::SSH::Test::Script
|
20
|
+
# and seed it with the necessary events to power the initialization of the
|
21
|
+
# connection.
|
22
|
+
def initialize
|
23
|
+
extend(Net::SSH::Transport::PacketStream)
|
24
|
+
super "SSH-2.0-Test\r\n"
|
25
|
+
|
26
|
+
@script = Script.new
|
27
|
+
|
28
|
+
script.gets(:kexinit, 1, 2, 3, 4, "test", "ssh-rsa", "none", "none", "none", "none", "none", "none", "", "", false)
|
29
|
+
script.sends(:kexinit)
|
30
|
+
script.sends(:newkeys)
|
31
|
+
script.gets(:newkeys)
|
32
|
+
end
|
33
|
+
|
34
|
+
# This doesn't actually do anything, since we don't really care what gets
|
35
|
+
# written.
|
36
|
+
def write(data)
|
37
|
+
# black hole, because we don't actually care about what gets written
|
38
|
+
end
|
39
|
+
|
40
|
+
# Allows the socket to also mimic a socket factory, simply returning
|
41
|
+
# +self+.
|
42
|
+
def open(host, port)
|
43
|
+
@host, @port = host, port
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a sockaddr struct for the port and host that were used when the
|
48
|
+
# socket was instantiated.
|
49
|
+
def getpeername
|
50
|
+
::Socket.sockaddr_in(port, host)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Alias to #read, but never returns nil (returns an empty string instead).
|
54
|
+
def recv(n)
|
55
|
+
read(n) || ""
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end; end; end
|
@@ -0,0 +1,384 @@
|
|
1
|
+
require 'net/ssh/buffer'
|
2
|
+
require 'net/ssh/known_hosts'
|
3
|
+
require 'net/ssh/loggable'
|
4
|
+
require 'net/ssh/transport/cipher_factory'
|
5
|
+
require 'net/ssh/transport/constants'
|
6
|
+
require 'net/ssh/transport/hmac'
|
7
|
+
require 'net/ssh/transport/kex'
|
8
|
+
require 'net/ssh/transport/server_version'
|
9
|
+
|
10
|
+
module Net; module SSH; module Transport
|
11
|
+
|
12
|
+
# Implements the higher-level logic behind an SSH key-exchange. It handles
|
13
|
+
# both the initial exchange, as well as subsequent re-exchanges (as needed).
|
14
|
+
# It also encapsulates the negotiation of the algorithms, and provides a
|
15
|
+
# single point of access to the negotiated algorithms.
|
16
|
+
#
|
17
|
+
# You will never instantiate or reference this directly. It is used
|
18
|
+
# internally by the transport layer.
|
19
|
+
class Algorithms
|
20
|
+
include Constants, Loggable
|
21
|
+
|
22
|
+
# Define the default algorithms, in order of preference, supported by
|
23
|
+
# Net::SSH.
|
24
|
+
ALGORITHMS = {
|
25
|
+
:host_key => %w(ssh-rsa ssh-dss),
|
26
|
+
:kex => %w(diffie-hellman-group-exchange-sha1
|
27
|
+
diffie-hellman-group1-sha1),
|
28
|
+
:encryption => %w(aes128-cbc 3des-cbc blowfish-cbc cast128-cbc
|
29
|
+
aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se
|
30
|
+
idea-cbc none),
|
31
|
+
:hmac => %w(hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96 none),
|
32
|
+
:compression => %w(none zlib@openssh.com zlib),
|
33
|
+
:language => %w()
|
34
|
+
}
|
35
|
+
|
36
|
+
# The underlying transport layer session that supports this object
|
37
|
+
attr_reader :session
|
38
|
+
|
39
|
+
# The hash of options used to initialize this object
|
40
|
+
attr_reader :options
|
41
|
+
|
42
|
+
# The kex algorithm to use settled on between the client and server.
|
43
|
+
attr_reader :kex
|
44
|
+
|
45
|
+
# The type of host key that will be used for this session.
|
46
|
+
attr_reader :host_key
|
47
|
+
|
48
|
+
# The type of the cipher to use to encrypt packets sent from the client to
|
49
|
+
# the server.
|
50
|
+
attr_reader :encryption_client
|
51
|
+
|
52
|
+
# The type of the cipher to use to decrypt packets arriving from the server.
|
53
|
+
attr_reader :encryption_server
|
54
|
+
|
55
|
+
# The type of HMAC to use to sign packets sent by the client.
|
56
|
+
attr_reader :hmac_client
|
57
|
+
|
58
|
+
# The type of HMAC to use to validate packets arriving from the server.
|
59
|
+
attr_reader :hmac_server
|
60
|
+
|
61
|
+
# The type of compression to use to compress packets being sent by the client.
|
62
|
+
attr_reader :compression_client
|
63
|
+
|
64
|
+
# The type of compression to use to decompress packets arriving from the server.
|
65
|
+
attr_reader :compression_server
|
66
|
+
|
67
|
+
# The language that will be used in messages sent by the client.
|
68
|
+
attr_reader :language_client
|
69
|
+
|
70
|
+
# The language that will be used in messages sent from the server.
|
71
|
+
attr_reader :language_server
|
72
|
+
|
73
|
+
# The hash of algorithms preferred by the client, which will be told to
|
74
|
+
# the server during algorithm negotiation.
|
75
|
+
attr_reader :algorithms
|
76
|
+
|
77
|
+
# The session-id for this session, as decided during the initial key exchange.
|
78
|
+
attr_reader :session_id
|
79
|
+
|
80
|
+
# Returns true if the given packet can be processed during a key-exchange.
|
81
|
+
def self.allowed_packet?(packet)
|
82
|
+
( 1.. 4).include?(packet.type) ||
|
83
|
+
( 6..19).include?(packet.type) ||
|
84
|
+
(21..49).include?(packet.type)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Instantiates a new Algorithms object, and prepares the hash of preferred
|
88
|
+
# algorithms based on the options parameter and the ALGORITHMS constant.
|
89
|
+
def initialize(session, options={})
|
90
|
+
@session = session
|
91
|
+
@logger = session.logger
|
92
|
+
@options = options
|
93
|
+
@algorithms = {}
|
94
|
+
@pending = @initialized = false
|
95
|
+
@client_packet = @server_packet = nil
|
96
|
+
prepare_preferred_algorithms!
|
97
|
+
end
|
98
|
+
|
99
|
+
# Request a rekey operation. This will return immediately, and does not
|
100
|
+
# actually perform the rekey operation. It does cause the session to change
|
101
|
+
# state, however--until the key exchange finishes, no new packets will be
|
102
|
+
# processed.
|
103
|
+
def rekey!
|
104
|
+
@client_packet = @server_packet = nil
|
105
|
+
@initialized = false
|
106
|
+
send_kexinit
|
107
|
+
end
|
108
|
+
|
109
|
+
# Called by the transport layer when a KEXINIT packet is recieved, indicating
|
110
|
+
# that the server wants to exchange keys. This can be spontaneous, or it
|
111
|
+
# can be in response to a client-initiated rekey request (see #rekey!). Either
|
112
|
+
# way, this will block until the key exchange completes.
|
113
|
+
def accept_kexinit(packet)
|
114
|
+
info { "got KEXINIT from server" }
|
115
|
+
@server_data = parse_server_algorithm_packet(packet)
|
116
|
+
@server_packet = @server_data[:raw]
|
117
|
+
if !pending?
|
118
|
+
send_kexinit
|
119
|
+
else
|
120
|
+
proceed!
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# A convenience method for accessing the list of preferred types for a
|
125
|
+
# specific algorithm (see #algorithms).
|
126
|
+
def [](key)
|
127
|
+
algorithms[key]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns +true+ if a key-exchange is pending. This will be true from the
|
131
|
+
# moment either the client or server requests the key exchange, until the
|
132
|
+
# exchange completes. While an exchange is pending, only a limited number
|
133
|
+
# of packets are allowed, so event processing essentially stops during this
|
134
|
+
# period.
|
135
|
+
def pending?
|
136
|
+
@pending
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns true if no exchange is pending, and otherwise returns true or
|
140
|
+
# false depending on whether the given packet is of a type that is allowed
|
141
|
+
# during a key exchange.
|
142
|
+
def allow?(packet)
|
143
|
+
!pending? || Algorithms.allowed_packet?(packet)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns true if the algorithms have been negotiated at all.
|
147
|
+
def initialized?
|
148
|
+
@initialized
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
# Sends a KEXINIT packet to the server. If a server KEXINIT has already
|
154
|
+
# been received, this will then invoke #proceed! to proceed with the key
|
155
|
+
# exchange, otherwise it returns immediately (but sets the object to the
|
156
|
+
# pending state).
|
157
|
+
def send_kexinit
|
158
|
+
info { "sending KEXINIT" }
|
159
|
+
@pending = true
|
160
|
+
packet = build_client_algorithm_packet
|
161
|
+
@client_packet = packet.to_s
|
162
|
+
session.send_message(packet)
|
163
|
+
proceed! if @server_packet
|
164
|
+
end
|
165
|
+
|
166
|
+
# After both client and server have sent their KEXINIT packets, this
|
167
|
+
# will do the algorithm negotiation and key exchange. Once both finish,
|
168
|
+
# the object leaves the pending state and the method returns.
|
169
|
+
def proceed!
|
170
|
+
info { "negotiating algorithms" }
|
171
|
+
negotiate_algorithms
|
172
|
+
exchange_keys
|
173
|
+
@pending = false
|
174
|
+
end
|
175
|
+
|
176
|
+
# Prepares the list of preferred algorithms, based on the options hash
|
177
|
+
# that was given when the object was constructed, and the ALGORITHMS
|
178
|
+
# constant. Also, when determining the host_key type to use, the known
|
179
|
+
# hosts files are examined to see if the host has ever sent a host_key
|
180
|
+
# before, and if so, that key type is used as the preferred type for
|
181
|
+
# communicating with this server.
|
182
|
+
def prepare_preferred_algorithms!
|
183
|
+
options[:compression] = %w(zlib@openssh.com zlib) if options[:compression] == true
|
184
|
+
|
185
|
+
ALGORITHMS.each do |algorithm, list|
|
186
|
+
algorithms[algorithm] = list.dup
|
187
|
+
|
188
|
+
# apply the preferred algorithm order, if any
|
189
|
+
if options[algorithm]
|
190
|
+
algorithms[algorithm] = Array(options[algorithm]).compact.uniq
|
191
|
+
invalid = algorithms[algorithm].detect { |name| !ALGORITHMS[algorithm].include?(name) }
|
192
|
+
raise NotImplementedError, "unsupported #{algorithm} algorithm: `#{invalid}'" if invalid
|
193
|
+
|
194
|
+
# make sure all of our supported algorithms are tacked onto the
|
195
|
+
# end, so that if the user tries to give a list of which none are
|
196
|
+
# supported, we can still proceed.
|
197
|
+
list.each { |name| algorithms[algorithm] << name unless algorithms[algorithm].include?(name) }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# for convention, make sure our list has the same keys as the server
|
202
|
+
# list
|
203
|
+
|
204
|
+
algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
|
205
|
+
algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
|
206
|
+
algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
|
207
|
+
algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
|
208
|
+
|
209
|
+
if !options.key?(:host_key)
|
210
|
+
# make sure the host keys are specified in preference order, where any
|
211
|
+
# existing known key for the host has preference.
|
212
|
+
|
213
|
+
existing_keys = KnownHosts.search_for(options[:host_key_alias] || session.host_as_string, options)
|
214
|
+
host_keys = existing_keys.map { |key| key.ssh_type }.uniq
|
215
|
+
algorithms[:host_key].each do |name|
|
216
|
+
host_keys << name unless host_keys.include?(name)
|
217
|
+
end
|
218
|
+
algorithms[:host_key] = host_keys
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Parses a KEXINIT packet from the server.
|
223
|
+
def parse_server_algorithm_packet(packet)
|
224
|
+
data = { :raw => packet.content }
|
225
|
+
|
226
|
+
packet.read(16) # skip the cookie value
|
227
|
+
|
228
|
+
data[:kex] = packet.read_string.split(/,/)
|
229
|
+
data[:host_key] = packet.read_string.split(/,/)
|
230
|
+
data[:encryption_client] = packet.read_string.split(/,/)
|
231
|
+
data[:encryption_server] = packet.read_string.split(/,/)
|
232
|
+
data[:hmac_client] = packet.read_string.split(/,/)
|
233
|
+
data[:hmac_server] = packet.read_string.split(/,/)
|
234
|
+
data[:compression_client] = packet.read_string.split(/,/)
|
235
|
+
data[:compression_server] = packet.read_string.split(/,/)
|
236
|
+
data[:language_client] = packet.read_string.split(/,/)
|
237
|
+
data[:language_server] = packet.read_string.split(/,/)
|
238
|
+
|
239
|
+
# TODO: if first_kex_packet_follows, we need to try to skip the
|
240
|
+
# actual kexinit stuff and try to guess what the server is doing...
|
241
|
+
# need to read more about this scenario.
|
242
|
+
first_kex_packet_follows = packet.read_bool
|
243
|
+
|
244
|
+
return data
|
245
|
+
end
|
246
|
+
|
247
|
+
# Given the #algorithms map of preferred algorithm types, this constructs
|
248
|
+
# a KEXINIT packet to send to the server. It does not actually send it,
|
249
|
+
# it simply builds the packet and returns it.
|
250
|
+
def build_client_algorithm_packet
|
251
|
+
kex = algorithms[:kex ].join(",")
|
252
|
+
host_key = algorithms[:host_key ].join(",")
|
253
|
+
encryption = algorithms[:encryption ].join(",")
|
254
|
+
hmac = algorithms[:hmac ].join(",")
|
255
|
+
compression = algorithms[:compression].join(",")
|
256
|
+
language = algorithms[:language ].join(",")
|
257
|
+
|
258
|
+
Net::SSH::Buffer.from(:byte, KEXINIT,
|
259
|
+
:long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
|
260
|
+
:string, [kex, host_key, encryption, encryption, hmac, hmac],
|
261
|
+
:string, [compression, compression, language, language],
|
262
|
+
:bool, false, :long, 0)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Given the parsed server KEX packet, and the client's preferred algorithm
|
266
|
+
# lists in #algorithms, determine which preferred algorithms each has
|
267
|
+
# in common and set those as the selected algorithms. If, for any algorithm,
|
268
|
+
# no type can be settled on, an exception is raised.
|
269
|
+
def negotiate_algorithms
|
270
|
+
@kex = negotiate(:kex)
|
271
|
+
@host_key = negotiate(:host_key)
|
272
|
+
@encryption_client = negotiate(:encryption_client)
|
273
|
+
@encryption_server = negotiate(:encryption_server)
|
274
|
+
@hmac_client = negotiate(:hmac_client)
|
275
|
+
@hmac_server = negotiate(:hmac_server)
|
276
|
+
@compression_client = negotiate(:compression_client)
|
277
|
+
@compression_server = negotiate(:compression_server)
|
278
|
+
@language_client = negotiate(:language_client) rescue ""
|
279
|
+
@language_server = negotiate(:language_server) rescue ""
|
280
|
+
|
281
|
+
debug do
|
282
|
+
"negotiated:\n" +
|
283
|
+
[:kex, :host_key, :encryption_server, :encryption_client, :hmac_client, :hmac_server, :compression_client, :compression_server, :language_client, :language_server].map do |key|
|
284
|
+
"* #{key}: #{instance_variable_get("@#{key}")}"
|
285
|
+
end.join("\n")
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Negotiates a single algorithm based on the preferences reported by the
|
290
|
+
# server and those set by the client. This is called by
|
291
|
+
# #negotiate_algorithms.
|
292
|
+
def negotiate(algorithm)
|
293
|
+
match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
|
294
|
+
|
295
|
+
if match.nil?
|
296
|
+
raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm"
|
297
|
+
end
|
298
|
+
|
299
|
+
return match
|
300
|
+
end
|
301
|
+
|
302
|
+
# Considers the sizes of the keys and block-sizes for the selected ciphers,
|
303
|
+
# and the lengths of the hmacs, and returns the largest as the byte requirement
|
304
|
+
# for the key-exchange algorithm.
|
305
|
+
def kex_byte_requirement
|
306
|
+
sizes = [8] # require at least 8 bytes
|
307
|
+
|
308
|
+
sizes.concat(CipherFactory.get_lengths(encryption_client))
|
309
|
+
sizes.concat(CipherFactory.get_lengths(encryption_server))
|
310
|
+
|
311
|
+
sizes << HMAC.key_length(hmac_client)
|
312
|
+
sizes << HMAC.key_length(hmac_server)
|
313
|
+
|
314
|
+
sizes.max
|
315
|
+
end
|
316
|
+
|
317
|
+
# Instantiates one of the Transport::Kex classes (based on the negotiated
|
318
|
+
# kex algorithm), and uses it to exchange keys. Then, the ciphers and
|
319
|
+
# HMACs are initialized and fed to the transport layer, to be used in
|
320
|
+
# further communication with the server.
|
321
|
+
def exchange_keys
|
322
|
+
debug { "exchanging keys" }
|
323
|
+
|
324
|
+
algorithm = Kex::MAP[kex].new(self, session,
|
325
|
+
:client_version_string => Net::SSH::Transport::ServerVersion::PROTO_VERSION,
|
326
|
+
:server_version_string => session.server_version.version,
|
327
|
+
:server_algorithm_packet => @server_packet,
|
328
|
+
:client_algorithm_packet => @client_packet,
|
329
|
+
:need_bytes => kex_byte_requirement,
|
330
|
+
:logger => logger)
|
331
|
+
result = algorithm.exchange_keys
|
332
|
+
|
333
|
+
secret = result[:shared_secret].to_ssh
|
334
|
+
hash = result[:session_id]
|
335
|
+
digester = result[:hashing_algorithm]
|
336
|
+
|
337
|
+
@session_id ||= hash
|
338
|
+
|
339
|
+
key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
|
340
|
+
|
341
|
+
iv_client = key["A"]
|
342
|
+
iv_server = key["B"]
|
343
|
+
key_client = key["C"]
|
344
|
+
key_server = key["D"]
|
345
|
+
mac_key_client = key["E"]
|
346
|
+
mac_key_server = key["F"]
|
347
|
+
|
348
|
+
parameters = { :iv => iv_client, :key => key_client, :shared => secret,
|
349
|
+
:hash => hash, :digester => digester }
|
350
|
+
|
351
|
+
cipher_client = CipherFactory.get(encryption_client, parameters.merge(:encrypt => true))
|
352
|
+
cipher_server = CipherFactory.get(encryption_server, parameters.merge(:iv => iv_server, :key => key_server, :decrypt => true))
|
353
|
+
|
354
|
+
mac_client = HMAC.get(hmac_client, mac_key_client)
|
355
|
+
mac_server = HMAC.get(hmac_server, mac_key_server)
|
356
|
+
|
357
|
+
session.configure_client :cipher => cipher_client, :hmac => mac_client,
|
358
|
+
:compression => normalize_compression_name(compression_client),
|
359
|
+
:compression_level => options[:compression_level],
|
360
|
+
:rekey_limit => options[:rekey_limit],
|
361
|
+
:max_packets => options[:rekey_packet_limit],
|
362
|
+
:max_blocks => options[:rekey_blocks_limit]
|
363
|
+
|
364
|
+
session.configure_server :cipher => cipher_server, :hmac => mac_server,
|
365
|
+
:compression => normalize_compression_name(compression_server),
|
366
|
+
:rekey_limit => options[:rekey_limit],
|
367
|
+
:max_packets => options[:rekey_packet_limit],
|
368
|
+
:max_blocks => options[:rekey_blocks_limit]
|
369
|
+
|
370
|
+
@initialized = true
|
371
|
+
end
|
372
|
+
|
373
|
+
# Given the SSH name for some compression algorithm, return a normalized
|
374
|
+
# name as a symbol.
|
375
|
+
def normalize_compression_name(name)
|
376
|
+
case name
|
377
|
+
when "none" then false
|
378
|
+
when "zlib" then :standard
|
379
|
+
when "zlib@openssh.com" then :delayed
|
380
|
+
else raise ArgumentError, "unknown compression type `#{name}'"
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end; end; end
|