minmb-net-ssh 2.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG.rdoc +291 -0
  2. data/Manifest +132 -0
  3. data/README.rdoc +184 -0
  4. data/Rakefile +86 -0
  5. data/Rudyfile +96 -0
  6. data/THANKS.rdoc +19 -0
  7. data/lib/net/ssh.rb +223 -0
  8. data/lib/net/ssh/authentication/agent.rb +23 -0
  9. data/lib/net/ssh/authentication/agent/java_pageant.rb +85 -0
  10. data/lib/net/ssh/authentication/agent/socket.rb +170 -0
  11. data/lib/net/ssh/authentication/constants.rb +18 -0
  12. data/lib/net/ssh/authentication/key_manager.rb +253 -0
  13. data/lib/net/ssh/authentication/methods/abstract.rb +60 -0
  14. data/lib/net/ssh/authentication/methods/hostbased.rb +75 -0
  15. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +70 -0
  16. data/lib/net/ssh/authentication/methods/password.rb +43 -0
  17. data/lib/net/ssh/authentication/methods/publickey.rb +96 -0
  18. data/lib/net/ssh/authentication/pageant.rb +301 -0
  19. data/lib/net/ssh/authentication/session.rb +154 -0
  20. data/lib/net/ssh/buffer.rb +350 -0
  21. data/lib/net/ssh/buffered_io.rb +207 -0
  22. data/lib/net/ssh/config.rb +207 -0
  23. data/lib/net/ssh/connection/channel.rb +630 -0
  24. data/lib/net/ssh/connection/constants.rb +33 -0
  25. data/lib/net/ssh/connection/session.rb +603 -0
  26. data/lib/net/ssh/connection/term.rb +178 -0
  27. data/lib/net/ssh/errors.rb +88 -0
  28. data/lib/net/ssh/key_factory.rb +107 -0
  29. data/lib/net/ssh/known_hosts.rb +141 -0
  30. data/lib/net/ssh/loggable.rb +61 -0
  31. data/lib/net/ssh/packet.rb +102 -0
  32. data/lib/net/ssh/prompt.rb +93 -0
  33. data/lib/net/ssh/proxy/command.rb +75 -0
  34. data/lib/net/ssh/proxy/errors.rb +14 -0
  35. data/lib/net/ssh/proxy/http.rb +94 -0
  36. data/lib/net/ssh/proxy/socks4.rb +70 -0
  37. data/lib/net/ssh/proxy/socks5.rb +142 -0
  38. data/lib/net/ssh/ruby_compat.rb +77 -0
  39. data/lib/net/ssh/service/forward.rb +327 -0
  40. data/lib/net/ssh/test.rb +89 -0
  41. data/lib/net/ssh/test/channel.rb +129 -0
  42. data/lib/net/ssh/test/extensions.rb +152 -0
  43. data/lib/net/ssh/test/kex.rb +44 -0
  44. data/lib/net/ssh/test/local_packet.rb +51 -0
  45. data/lib/net/ssh/test/packet.rb +81 -0
  46. data/lib/net/ssh/test/remote_packet.rb +38 -0
  47. data/lib/net/ssh/test/script.rb +157 -0
  48. data/lib/net/ssh/test/socket.rb +64 -0
  49. data/lib/net/ssh/transport/algorithms.rb +407 -0
  50. data/lib/net/ssh/transport/cipher_factory.rb +106 -0
  51. data/lib/net/ssh/transport/constants.rb +32 -0
  52. data/lib/net/ssh/transport/ctr.rb +95 -0
  53. data/lib/net/ssh/transport/hmac.rb +45 -0
  54. data/lib/net/ssh/transport/hmac/abstract.rb +79 -0
  55. data/lib/net/ssh/transport/hmac/md5.rb +12 -0
  56. data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  57. data/lib/net/ssh/transport/hmac/none.rb +15 -0
  58. data/lib/net/ssh/transport/hmac/ripemd160.rb +13 -0
  59. data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  60. data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  61. data/lib/net/ssh/transport/hmac/sha2_256.rb +15 -0
  62. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +13 -0
  63. data/lib/net/ssh/transport/hmac/sha2_512.rb +14 -0
  64. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +13 -0
  65. data/lib/net/ssh/transport/identity_cipher.rb +55 -0
  66. data/lib/net/ssh/transport/kex.rb +28 -0
  67. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +44 -0
  68. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +216 -0
  69. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +80 -0
  70. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +15 -0
  71. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +93 -0
  72. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +13 -0
  73. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +13 -0
  74. data/lib/net/ssh/transport/key_expander.rb +26 -0
  75. data/lib/net/ssh/transport/openssl.rb +237 -0
  76. data/lib/net/ssh/transport/packet_stream.rb +235 -0
  77. data/lib/net/ssh/transport/server_version.rb +71 -0
  78. data/lib/net/ssh/transport/session.rb +278 -0
  79. data/lib/net/ssh/transport/state.rb +206 -0
  80. data/lib/net/ssh/verifiers/lenient.rb +30 -0
  81. data/lib/net/ssh/verifiers/null.rb +12 -0
  82. data/lib/net/ssh/verifiers/strict.rb +53 -0
  83. data/lib/net/ssh/version.rb +62 -0
  84. data/net-ssh.gemspec +164 -0
  85. data/setup.rb +1585 -0
  86. data/support/arcfour_check.rb +20 -0
  87. data/support/ssh_tunnel_bug.rb +65 -0
  88. data/test/authentication/methods/common.rb +28 -0
  89. data/test/authentication/methods/test_abstract.rb +51 -0
  90. data/test/authentication/methods/test_hostbased.rb +114 -0
  91. data/test/authentication/methods/test_keyboard_interactive.rb +100 -0
  92. data/test/authentication/methods/test_password.rb +52 -0
  93. data/test/authentication/methods/test_publickey.rb +148 -0
  94. data/test/authentication/test_agent.rb +205 -0
  95. data/test/authentication/test_key_manager.rb +218 -0
  96. data/test/authentication/test_session.rb +106 -0
  97. data/test/common.rb +107 -0
  98. data/test/configs/eqsign +3 -0
  99. data/test/configs/exact_match +8 -0
  100. data/test/configs/host_plus +10 -0
  101. data/test/configs/multihost +4 -0
  102. data/test/configs/wild_cards +14 -0
  103. data/test/connection/test_channel.rb +467 -0
  104. data/test/connection/test_session.rb +488 -0
  105. data/test/known_hosts/github +1 -0
  106. data/test/test_all.rb +9 -0
  107. data/test/test_buffer.rb +426 -0
  108. data/test/test_buffered_io.rb +63 -0
  109. data/test/test_config.rb +120 -0
  110. data/test/test_key_factory.rb +121 -0
  111. data/test/test_known_hosts.rb +13 -0
  112. data/test/transport/hmac/test_md5.rb +39 -0
  113. data/test/transport/hmac/test_md5_96.rb +25 -0
  114. data/test/transport/hmac/test_none.rb +34 -0
  115. data/test/transport/hmac/test_ripemd160.rb +34 -0
  116. data/test/transport/hmac/test_sha1.rb +34 -0
  117. data/test/transport/hmac/test_sha1_96.rb +25 -0
  118. data/test/transport/hmac/test_sha2_256.rb +35 -0
  119. data/test/transport/hmac/test_sha2_256_96.rb +25 -0
  120. data/test/transport/hmac/test_sha2_512.rb +35 -0
  121. data/test/transport/hmac/test_sha2_512_96.rb +25 -0
  122. data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +13 -0
  123. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
  124. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
  125. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +33 -0
  126. data/test/transport/kex/test_ecdh_sha2_nistp256.rb +161 -0
  127. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +37 -0
  128. data/test/transport/kex/test_ecdh_sha2_nistp521.rb +37 -0
  129. data/test/transport/test_algorithms.rb +330 -0
  130. data/test/transport/test_cipher_factory.rb +441 -0
  131. data/test/transport/test_hmac.rb +34 -0
  132. data/test/transport/test_identity_cipher.rb +40 -0
  133. data/test/transport/test_packet_stream.rb +1745 -0
  134. data/test/transport/test_server_version.rb +78 -0
  135. data/test/transport/test_session.rb +315 -0
  136. data/test/transport/test_state.rb +179 -0
  137. metadata +208 -0
@@ -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,64 @@
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
+
58
+ def readpartial(n)
59
+ recv(n)
60
+ end
61
+
62
+ end
63
+
64
+ end; end; end
@@ -0,0 +1,407 @@
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
+ diffie-hellman-group14-sha1
29
+ diffie-hellman-group-exchange-sha256),
30
+ :encryption => %w(aes128-cbc 3des-cbc blowfish-cbc cast128-cbc
31
+ aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se
32
+ idea-cbc none arcfour128 arcfour256 arcfour
33
+ aes128-ctr aes192-ctr aes256-ctr
34
+ camellia128-cbc camellia192-cbc camellia256-cbc
35
+ camellia128-cbc@openssh.org
36
+ camellia192-cbc@openssh.org
37
+ camellia256-cbc@openssh.org
38
+ camellia128-ctr camellia192-ctr camellia256-ctr
39
+ camellia128-ctr@openssh.org
40
+ camellia192-ctr@openssh.org
41
+ camellia256-ctr@openssh.org
42
+ cast128-ctr blowfish-ctr 3des-ctr
43
+ ),
44
+ :hmac => %w(hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96
45
+ hmac-ripemd160 hmac-ripemd160@openssh.com
46
+ hmac-sha2-256 hmac-sha2-512 hmac-sha2-256-96
47
+ hmac-sha2-512-96 none),
48
+ :compression => %w(none zlib@openssh.com zlib),
49
+ :language => %w()
50
+ }
51
+ if defined?(OpenSSL::PKey::EC)
52
+ ALGORITHMS[:host_key] += %w(ecdsa-sha2-nistp256
53
+ ecdsa-sha2-nistp384
54
+ ecdsa-sha2-nistp521)
55
+ ALGORITHMS[:kex] += %w(ecdh-sha2-nistp256
56
+ ecdh-sha2-nistp384
57
+ ecdh-sha2-nistp521)
58
+ end
59
+
60
+ # The underlying transport layer session that supports this object
61
+ attr_reader :session
62
+
63
+ # The hash of options used to initialize this object
64
+ attr_reader :options
65
+
66
+ # The kex algorithm to use settled on between the client and server.
67
+ attr_reader :kex
68
+
69
+ # The type of host key that will be used for this session.
70
+ attr_reader :host_key
71
+
72
+ # The type of the cipher to use to encrypt packets sent from the client to
73
+ # the server.
74
+ attr_reader :encryption_client
75
+
76
+ # The type of the cipher to use to decrypt packets arriving from the server.
77
+ attr_reader :encryption_server
78
+
79
+ # The type of HMAC to use to sign packets sent by the client.
80
+ attr_reader :hmac_client
81
+
82
+ # The type of HMAC to use to validate packets arriving from the server.
83
+ attr_reader :hmac_server
84
+
85
+ # The type of compression to use to compress packets being sent by the client.
86
+ attr_reader :compression_client
87
+
88
+ # The type of compression to use to decompress packets arriving from the server.
89
+ attr_reader :compression_server
90
+
91
+ # The language that will be used in messages sent by the client.
92
+ attr_reader :language_client
93
+
94
+ # The language that will be used in messages sent from the server.
95
+ attr_reader :language_server
96
+
97
+ # The hash of algorithms preferred by the client, which will be told to
98
+ # the server during algorithm negotiation.
99
+ attr_reader :algorithms
100
+
101
+ # The session-id for this session, as decided during the initial key exchange.
102
+ attr_reader :session_id
103
+
104
+ # Returns true if the given packet can be processed during a key-exchange.
105
+ def self.allowed_packet?(packet)
106
+ ( 1.. 4).include?(packet.type) ||
107
+ ( 6..19).include?(packet.type) ||
108
+ (21..49).include?(packet.type)
109
+ end
110
+
111
+ # Instantiates a new Algorithms object, and prepares the hash of preferred
112
+ # algorithms based on the options parameter and the ALGORITHMS constant.
113
+ def initialize(session, options={})
114
+ @session = session
115
+ @logger = session.logger
116
+ @options = options
117
+ @algorithms = {}
118
+ @pending = @initialized = false
119
+ @client_packet = @server_packet = nil
120
+ prepare_preferred_algorithms!
121
+ end
122
+
123
+ # Request a rekey operation. This will return immediately, and does not
124
+ # actually perform the rekey operation. It does cause the session to change
125
+ # state, however--until the key exchange finishes, no new packets will be
126
+ # processed.
127
+ def rekey!
128
+ @client_packet = @server_packet = nil
129
+ @initialized = false
130
+ send_kexinit
131
+ end
132
+
133
+ # Called by the transport layer when a KEXINIT packet is recieved, indicating
134
+ # that the server wants to exchange keys. This can be spontaneous, or it
135
+ # can be in response to a client-initiated rekey request (see #rekey!). Either
136
+ # way, this will block until the key exchange completes.
137
+ def accept_kexinit(packet)
138
+ info { "got KEXINIT from server" }
139
+ @server_data = parse_server_algorithm_packet(packet)
140
+ @server_packet = @server_data[:raw]
141
+ if !pending?
142
+ send_kexinit
143
+ else
144
+ proceed!
145
+ end
146
+ end
147
+
148
+ # A convenience method for accessing the list of preferred types for a
149
+ # specific algorithm (see #algorithms).
150
+ def [](key)
151
+ algorithms[key]
152
+ end
153
+
154
+ # Returns +true+ if a key-exchange is pending. This will be true from the
155
+ # moment either the client or server requests the key exchange, until the
156
+ # exchange completes. While an exchange is pending, only a limited number
157
+ # of packets are allowed, so event processing essentially stops during this
158
+ # period.
159
+ def pending?
160
+ @pending
161
+ end
162
+
163
+ # Returns true if no exchange is pending, and otherwise returns true or
164
+ # false depending on whether the given packet is of a type that is allowed
165
+ # during a key exchange.
166
+ def allow?(packet)
167
+ !pending? || Algorithms.allowed_packet?(packet)
168
+ end
169
+
170
+ # Returns true if the algorithms have been negotiated at all.
171
+ def initialized?
172
+ @initialized
173
+ end
174
+
175
+ private
176
+
177
+ # Sends a KEXINIT packet to the server. If a server KEXINIT has already
178
+ # been received, this will then invoke #proceed! to proceed with the key
179
+ # exchange, otherwise it returns immediately (but sets the object to the
180
+ # pending state).
181
+ def send_kexinit
182
+ info { "sending KEXINIT" }
183
+ @pending = true
184
+ packet = build_client_algorithm_packet
185
+ @client_packet = packet.to_s
186
+ session.send_message(packet)
187
+ proceed! if @server_packet
188
+ end
189
+
190
+ # After both client and server have sent their KEXINIT packets, this
191
+ # will do the algorithm negotiation and key exchange. Once both finish,
192
+ # the object leaves the pending state and the method returns.
193
+ def proceed!
194
+ info { "negotiating algorithms" }
195
+ negotiate_algorithms
196
+ exchange_keys
197
+ @pending = false
198
+ end
199
+
200
+ # Prepares the list of preferred algorithms, based on the options hash
201
+ # that was given when the object was constructed, and the ALGORITHMS
202
+ # constant. Also, when determining the host_key type to use, the known
203
+ # hosts files are examined to see if the host has ever sent a host_key
204
+ # before, and if so, that key type is used as the preferred type for
205
+ # communicating with this server.
206
+ def prepare_preferred_algorithms!
207
+ options[:compression] = %w(zlib@openssh.com zlib) if options[:compression] == true
208
+
209
+ ALGORITHMS.each do |algorithm, list|
210
+ algorithms[algorithm] = list.dup
211
+
212
+ # apply the preferred algorithm order, if any
213
+ if options[algorithm]
214
+ algorithms[algorithm] = Array(options[algorithm]).compact.uniq
215
+ invalid = algorithms[algorithm].detect { |name| !ALGORITHMS[algorithm].include?(name) }
216
+ raise NotImplementedError, "unsupported #{algorithm} algorithm: `#{invalid}'" if invalid
217
+
218
+ # make sure all of our supported algorithms are tacked onto the
219
+ # end, so that if the user tries to give a list of which none are
220
+ # supported, we can still proceed.
221
+ list.each { |name| algorithms[algorithm] << name unless algorithms[algorithm].include?(name) }
222
+ end
223
+ end
224
+
225
+ # for convention, make sure our list has the same keys as the server
226
+ # list
227
+
228
+ algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
229
+ algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
230
+ algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
231
+ algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
232
+
233
+ if !options.key?(:host_key)
234
+ # make sure the host keys are specified in preference order, where any
235
+ # existing known key for the host has preference.
236
+
237
+ existing_keys = KnownHosts.search_for(options[:host_key_alias] || session.host_as_string, options)
238
+ host_keys = existing_keys.map { |key| key.ssh_type }.uniq
239
+ algorithms[:host_key].each do |name|
240
+ host_keys << name unless host_keys.include?(name)
241
+ end
242
+ algorithms[:host_key] = host_keys
243
+ end
244
+ end
245
+
246
+ # Parses a KEXINIT packet from the server.
247
+ def parse_server_algorithm_packet(packet)
248
+ data = { :raw => packet.content }
249
+
250
+ packet.read(16) # skip the cookie value
251
+
252
+ data[:kex] = packet.read_string.split(/,/)
253
+ data[:host_key] = packet.read_string.split(/,/)
254
+ data[:encryption_client] = packet.read_string.split(/,/)
255
+ data[:encryption_server] = packet.read_string.split(/,/)
256
+ data[:hmac_client] = packet.read_string.split(/,/)
257
+ data[:hmac_server] = packet.read_string.split(/,/)
258
+ data[:compression_client] = packet.read_string.split(/,/)
259
+ data[:compression_server] = packet.read_string.split(/,/)
260
+ data[:language_client] = packet.read_string.split(/,/)
261
+ data[:language_server] = packet.read_string.split(/,/)
262
+
263
+ # TODO: if first_kex_packet_follows, we need to try to skip the
264
+ # actual kexinit stuff and try to guess what the server is doing...
265
+ # need to read more about this scenario.
266
+ first_kex_packet_follows = packet.read_bool
267
+
268
+ return data
269
+ end
270
+
271
+ # Given the #algorithms map of preferred algorithm types, this constructs
272
+ # a KEXINIT packet to send to the server. It does not actually send it,
273
+ # it simply builds the packet and returns it.
274
+ def build_client_algorithm_packet
275
+ kex = algorithms[:kex ].join(",")
276
+ host_key = algorithms[:host_key ].join(",")
277
+ encryption = algorithms[:encryption ].join(",")
278
+ hmac = algorithms[:hmac ].join(",")
279
+ compression = algorithms[:compression].join(",")
280
+ language = algorithms[:language ].join(",")
281
+
282
+ Net::SSH::Buffer.from(:byte, KEXINIT,
283
+ :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
284
+ :string, [kex, host_key, encryption, encryption, hmac, hmac],
285
+ :string, [compression, compression, language, language],
286
+ :bool, false, :long, 0)
287
+ end
288
+
289
+ # Given the parsed server KEX packet, and the client's preferred algorithm
290
+ # lists in #algorithms, determine which preferred algorithms each has
291
+ # in common and set those as the selected algorithms. If, for any algorithm,
292
+ # no type can be settled on, an exception is raised.
293
+ def negotiate_algorithms
294
+ @kex = negotiate(:kex)
295
+ @host_key = negotiate(:host_key)
296
+ @encryption_client = negotiate(:encryption_client)
297
+ @encryption_server = negotiate(:encryption_server)
298
+ @hmac_client = negotiate(:hmac_client)
299
+ @hmac_server = negotiate(:hmac_server)
300
+ @compression_client = negotiate(:compression_client)
301
+ @compression_server = negotiate(:compression_server)
302
+ @language_client = negotiate(:language_client) rescue ""
303
+ @language_server = negotiate(:language_server) rescue ""
304
+
305
+ debug do
306
+ "negotiated:\n" +
307
+ [:kex, :host_key, :encryption_server, :encryption_client, :hmac_client, :hmac_server, :compression_client, :compression_server, :language_client, :language_server].map do |key|
308
+ "* #{key}: #{instance_variable_get("@#{key}")}"
309
+ end.join("\n")
310
+ end
311
+ end
312
+
313
+ # Negotiates a single algorithm based on the preferences reported by the
314
+ # server and those set by the client. This is called by
315
+ # #negotiate_algorithms.
316
+ def negotiate(algorithm)
317
+ match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
318
+
319
+ if match.nil?
320
+ raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm"
321
+ end
322
+
323
+ return match
324
+ end
325
+
326
+ # Considers the sizes of the keys and block-sizes for the selected ciphers,
327
+ # and the lengths of the hmacs, and returns the largest as the byte requirement
328
+ # for the key-exchange algorithm.
329
+ def kex_byte_requirement
330
+ sizes = [8] # require at least 8 bytes
331
+
332
+ sizes.concat(CipherFactory.get_lengths(encryption_client))
333
+ sizes.concat(CipherFactory.get_lengths(encryption_server))
334
+
335
+ sizes << HMAC.key_length(hmac_client)
336
+ sizes << HMAC.key_length(hmac_server)
337
+
338
+ sizes.max
339
+ end
340
+
341
+ # Instantiates one of the Transport::Kex classes (based on the negotiated
342
+ # kex algorithm), and uses it to exchange keys. Then, the ciphers and
343
+ # HMACs are initialized and fed to the transport layer, to be used in
344
+ # further communication with the server.
345
+ def exchange_keys
346
+ debug { "exchanging keys" }
347
+
348
+ algorithm = Kex::MAP[kex].new(self, session,
349
+ :client_version_string => Net::SSH::Transport::ServerVersion::PROTO_VERSION,
350
+ :server_version_string => session.server_version.version,
351
+ :server_algorithm_packet => @server_packet,
352
+ :client_algorithm_packet => @client_packet,
353
+ :need_bytes => kex_byte_requirement,
354
+ :logger => logger)
355
+ result = algorithm.exchange_keys
356
+
357
+ secret = result[:shared_secret].to_ssh
358
+ hash = result[:session_id]
359
+ digester = result[:hashing_algorithm]
360
+
361
+ @session_id ||= hash
362
+
363
+ key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
364
+
365
+ iv_client = key["A"]
366
+ iv_server = key["B"]
367
+ key_client = key["C"]
368
+ key_server = key["D"]
369
+ mac_key_client = key["E"]
370
+ mac_key_server = key["F"]
371
+
372
+ parameters = { :shared => secret, :hash => hash, :digester => digester }
373
+
374
+ cipher_client = CipherFactory.get(encryption_client, parameters.merge(:iv => iv_client, :key => key_client, :encrypt => true))
375
+ cipher_server = CipherFactory.get(encryption_server, parameters.merge(:iv => iv_server, :key => key_server, :decrypt => true))
376
+
377
+ mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
378
+ mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
379
+
380
+ session.configure_client :cipher => cipher_client, :hmac => mac_client,
381
+ :compression => normalize_compression_name(compression_client),
382
+ :compression_level => options[:compression_level],
383
+ :rekey_limit => options[:rekey_limit],
384
+ :max_packets => options[:rekey_packet_limit],
385
+ :max_blocks => options[:rekey_blocks_limit]
386
+
387
+ session.configure_server :cipher => cipher_server, :hmac => mac_server,
388
+ :compression => normalize_compression_name(compression_server),
389
+ :rekey_limit => options[:rekey_limit],
390
+ :max_packets => options[:rekey_packet_limit],
391
+ :max_blocks => options[:rekey_blocks_limit]
392
+
393
+ @initialized = true
394
+ end
395
+
396
+ # Given the SSH name for some compression algorithm, return a normalized
397
+ # name as a symbol.
398
+ def normalize_compression_name(name)
399
+ case name
400
+ when "none" then false
401
+ when "zlib" then :standard
402
+ when "zlib@openssh.com" then :delayed
403
+ else raise ArgumentError, "unknown compression type `#{name}'"
404
+ end
405
+ end
406
+ end
407
+ end; end; end