eximius-net-ssh 6.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +6 -0
  3. data/.github/config/rubocop_linter_action.yml +4 -0
  4. data/.github/workflows/ci-with-docker.yml +44 -0
  5. data/.github/workflows/ci.yml +87 -0
  6. data/.github/workflows/rubocop.yml +13 -0
  7. data/.gitignore +13 -0
  8. data/.rubocop.yml +22 -0
  9. data/.rubocop_todo.yml +1072 -0
  10. data/CHANGES.txt +698 -0
  11. data/Dockerfile +27 -0
  12. data/Dockerfile.openssl3 +17 -0
  13. data/Gemfile +13 -0
  14. data/Gemfile.noed25519 +12 -0
  15. data/ISSUE_TEMPLATE.md +30 -0
  16. data/LICENSE.txt +19 -0
  17. data/Manifest +132 -0
  18. data/README.md +293 -0
  19. data/Rakefile +105 -0
  20. data/THANKS.txt +110 -0
  21. data/appveyor.yml +58 -0
  22. data/docker-compose.yml +23 -0
  23. data/lib/net/ssh/authentication/agent.rb +284 -0
  24. data/lib/net/ssh/authentication/certificate.rb +183 -0
  25. data/lib/net/ssh/authentication/constants.rb +20 -0
  26. data/lib/net/ssh/authentication/ed25519.rb +185 -0
  27. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  28. data/lib/net/ssh/authentication/key_manager.rb +310 -0
  29. data/lib/net/ssh/authentication/methods/abstract.rb +79 -0
  30. data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
  31. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
  32. data/lib/net/ssh/authentication/methods/none.rb +34 -0
  33. data/lib/net/ssh/authentication/methods/password.rb +80 -0
  34. data/lib/net/ssh/authentication/methods/publickey.rb +137 -0
  35. data/lib/net/ssh/authentication/pageant.rb +497 -0
  36. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  37. data/lib/net/ssh/authentication/session.rb +165 -0
  38. data/lib/net/ssh/buffer.rb +440 -0
  39. data/lib/net/ssh/buffered_io.rb +202 -0
  40. data/lib/net/ssh/config.rb +406 -0
  41. data/lib/net/ssh/connection/channel.rb +695 -0
  42. data/lib/net/ssh/connection/constants.rb +33 -0
  43. data/lib/net/ssh/connection/event_loop.rb +123 -0
  44. data/lib/net/ssh/connection/keepalive.rb +59 -0
  45. data/lib/net/ssh/connection/session.rb +712 -0
  46. data/lib/net/ssh/connection/term.rb +180 -0
  47. data/lib/net/ssh/errors.rb +106 -0
  48. data/lib/net/ssh/key_factory.rb +218 -0
  49. data/lib/net/ssh/known_hosts.rb +265 -0
  50. data/lib/net/ssh/loggable.rb +62 -0
  51. data/lib/net/ssh/packet.rb +106 -0
  52. data/lib/net/ssh/prompt.rb +62 -0
  53. data/lib/net/ssh/proxy/command.rb +123 -0
  54. data/lib/net/ssh/proxy/errors.rb +16 -0
  55. data/lib/net/ssh/proxy/http.rb +98 -0
  56. data/lib/net/ssh/proxy/https.rb +50 -0
  57. data/lib/net/ssh/proxy/jump.rb +54 -0
  58. data/lib/net/ssh/proxy/socks4.rb +67 -0
  59. data/lib/net/ssh/proxy/socks5.rb +140 -0
  60. data/lib/net/ssh/service/forward.rb +426 -0
  61. data/lib/net/ssh/test/channel.rb +147 -0
  62. data/lib/net/ssh/test/extensions.rb +173 -0
  63. data/lib/net/ssh/test/kex.rb +46 -0
  64. data/lib/net/ssh/test/local_packet.rb +53 -0
  65. data/lib/net/ssh/test/packet.rb +101 -0
  66. data/lib/net/ssh/test/remote_packet.rb +40 -0
  67. data/lib/net/ssh/test/script.rb +180 -0
  68. data/lib/net/ssh/test/socket.rb +65 -0
  69. data/lib/net/ssh/test.rb +94 -0
  70. data/lib/net/ssh/transport/algorithms.rb +502 -0
  71. data/lib/net/ssh/transport/cipher_factory.rb +103 -0
  72. data/lib/net/ssh/transport/constants.rb +40 -0
  73. data/lib/net/ssh/transport/ctr.rb +115 -0
  74. data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
  75. data/lib/net/ssh/transport/hmac/md5.rb +10 -0
  76. data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
  77. data/lib/net/ssh/transport/hmac/none.rb +13 -0
  78. data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
  79. data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
  80. data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
  81. data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
  82. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
  83. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  84. data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
  85. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
  86. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  87. data/lib/net/ssh/transport/hmac.rb +47 -0
  88. data/lib/net/ssh/transport/identity_cipher.rb +57 -0
  89. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  90. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  91. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  92. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  93. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
  94. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  95. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
  96. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
  97. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
  98. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
  99. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
  100. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
  101. data/lib/net/ssh/transport/kex.rb +31 -0
  102. data/lib/net/ssh/transport/key_expander.rb +30 -0
  103. data/lib/net/ssh/transport/openssl.rb +262 -0
  104. data/lib/net/ssh/transport/packet_stream.rb +280 -0
  105. data/lib/net/ssh/transport/server_version.rb +77 -0
  106. data/lib/net/ssh/transport/session.rb +354 -0
  107. data/lib/net/ssh/transport/state.rb +208 -0
  108. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  109. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  110. data/lib/net/ssh/verifiers/always.rb +58 -0
  111. data/lib/net/ssh/verifiers/never.rb +19 -0
  112. data/lib/net/ssh/version.rb +70 -0
  113. data/lib/net/ssh.rb +330 -0
  114. data/net-ssh-public_cert.pem +20 -0
  115. data/net-ssh.gemspec +44 -0
  116. data/support/ssh_tunnel_bug.rb +65 -0
  117. metadata +277 -0
@@ -0,0 +1,94 @@
1
+ require 'net/ssh/transport/session'
2
+ require 'net/ssh/connection/session'
3
+ require 'net/ssh/test/kex'
4
+ require 'net/ssh/test/socket'
5
+
6
+ module Net
7
+ module SSH
8
+ # This module may be used in unit tests, for when you want to test that your
9
+ # SSH state machines are really doing what you expect they are doing. You will
10
+ # typically include this module in your unit test class, and then build a
11
+ # "story" of expected sends and receives:
12
+ #
13
+ # require 'minitest/autorun'
14
+ # require 'net/ssh/test'
15
+ #
16
+ # class MyTest < Minitest::Test
17
+ # include Net::SSH::Test
18
+ #
19
+ # def test_exec_via_channel_works
20
+ # story do |session|
21
+ # channel = session.opens_channel
22
+ # channel.sends_exec "ls"
23
+ # channel.gets_data "result of ls"
24
+ # channel.gets_close
25
+ # channel.sends_close
26
+ # end
27
+ #
28
+ # assert_scripted do
29
+ # result = nil
30
+ #
31
+ # connection.open_channel do |ch|
32
+ # ch.exec("ls") do |success|
33
+ # ch.on_data { |c, data| result = data }
34
+ # ch.on_close { |c| c.close }
35
+ # end
36
+ # end
37
+ #
38
+ # connection.loop
39
+ # assert_equal "result of ls", result
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # See Net::SSH::Test::Channel and Net::SSH::Test::Script for more options.
45
+ #
46
+ # Note that the Net::SSH::Test system is rather finicky yet, and can be kind
47
+ # of frustrating to get working. Any suggestions for improvement will be
48
+ # welcome!
49
+ module Test
50
+ # If a block is given, yields the script for the test socket (#socket).
51
+ # Otherwise, simply returns the socket's script. See Net::SSH::Test::Script.
52
+ def story
53
+ Net::SSH::Test::Extensions::IO.with_test_extension { yield socket.script if block_given? }
54
+ return socket.script
55
+ end
56
+
57
+ # Returns the test socket instance to use for these tests (see
58
+ # Net::SSH::Test::Socket).
59
+ def socket(options = {})
60
+ @socket ||= Net::SSH::Test::Socket.new
61
+ end
62
+
63
+ # Returns the connection session (Net::SSH::Connection::Session) for use
64
+ # in these tests. It is a fully functional SSH session, operating over
65
+ # a mock socket (#socket).
66
+ def connection(options = {})
67
+ @connection ||= Net::SSH::Connection::Session.new(transport(options), options)
68
+ end
69
+
70
+ # Returns the transport session (Net::SSH::Transport::Session) for use
71
+ # in these tests. It is a fully functional SSH transport session, operating
72
+ # over a mock socket (#socket).
73
+ def transport(options = {})
74
+ @transport ||= Net::SSH::Transport::Session.new(
75
+ options[:host] || "localhost",
76
+ options.merge(kex: "test", host_key: "ssh-rsa", append_all_supported_algorithms: true, verify_host_key: :never, proxy: socket(options))
77
+ )
78
+ end
79
+
80
+ # First asserts that a story has been described (see #story). Then yields,
81
+ # and then asserts that all items described in the script have been
82
+ # processed. Typically, this is called immediately after a story has
83
+ # been built, and the SSH commands being tested are then executed within
84
+ # the block passed to this assertion.
85
+ def assert_scripted
86
+ raise "there is no script to be processed" if socket.script.events.empty?
87
+
88
+ Net::SSH::Test::Extensions::IO.with_test_extension { yield }
89
+ assert socket.script.events.empty?, "there should not be any remaining scripted events, but there are still" \
90
+ "#{socket.script.events.length} pending"
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,502 @@
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/kex/curve25519_sha256_loader'
9
+ require 'net/ssh/transport/server_version'
10
+ require 'net/ssh/authentication/ed25519_loader'
11
+
12
+ module Net
13
+ module SSH
14
+ module Transport
15
+ # Implements the higher-level logic behind an SSH key-exchange. It handles
16
+ # both the initial exchange, as well as subsequent re-exchanges (as needed).
17
+ # It also encapsulates the negotiation of the algorithms, and provides a
18
+ # single point of access to the negotiated algorithms.
19
+ #
20
+ # You will never instantiate or reference this directly. It is used
21
+ # internally by the transport layer.
22
+ class Algorithms
23
+ include Loggable
24
+ include Constants
25
+
26
+ # Define the default algorithms, in order of preference, supported by Net::SSH.
27
+ DEFAULT_ALGORITHMS = {
28
+ host_key: %w[ecdsa-sha2-nistp521-cert-v01@openssh.com
29
+ ecdsa-sha2-nistp384-cert-v01@openssh.com
30
+ ecdsa-sha2-nistp256-cert-v01@openssh.com
31
+ ecdsa-sha2-nistp521
32
+ ecdsa-sha2-nistp384
33
+ ecdsa-sha2-nistp256
34
+ ssh-rsa-cert-v01@openssh.com
35
+ ssh-rsa-cert-v00@openssh.com
36
+ ssh-rsa
37
+ rsa-sha2-256
38
+ rsa-sha2-512],
39
+
40
+ kex: %w[ecdh-sha2-nistp521
41
+ ecdh-sha2-nistp384
42
+ ecdh-sha2-nistp256
43
+ diffie-hellman-group-exchange-sha256
44
+ diffie-hellman-group14-sha256
45
+ diffie-hellman-group14-sha1],
46
+
47
+ encryption: %w[aes256-ctr aes192-ctr aes128-ctr],
48
+
49
+ hmac: %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com
50
+ hmac-sha2-512 hmac-sha2-256
51
+ hmac-sha1]
52
+ }.freeze
53
+
54
+ if Net::SSH::Authentication::ED25519Loader::LOADED
55
+ DEFAULT_ALGORITHMS[:host_key].unshift(
56
+ 'ssh-ed25519-cert-v01@openssh.com',
57
+ 'ssh-ed25519'
58
+ )
59
+ end
60
+
61
+ if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED
62
+ DEFAULT_ALGORITHMS[:kex].unshift(
63
+ 'curve25519-sha256',
64
+ 'curve25519-sha256@libssh.org'
65
+ )
66
+ end
67
+
68
+ # Define all algorithms, with the deprecated, supported by Net::SSH.
69
+ ALGORITHMS = {
70
+ host_key: DEFAULT_ALGORITHMS[:host_key] + %w[ssh-dss],
71
+
72
+ kex: DEFAULT_ALGORITHMS[:kex] +
73
+ %w[diffie-hellman-group-exchange-sha1
74
+ diffie-hellman-group1-sha1],
75
+
76
+ encryption: DEFAULT_ALGORITHMS[:encryption] +
77
+ %w[aes256-cbc aes192-cbc aes128-cbc
78
+ rijndael-cbc@lysator.liu.se
79
+ blowfish-ctr blowfish-cbc
80
+ cast128-ctr cast128-cbc
81
+ 3des-ctr 3des-cbc
82
+ idea-cbc
83
+ none],
84
+
85
+ hmac: DEFAULT_ALGORITHMS[:hmac] +
86
+ %w[hmac-sha2-512-96 hmac-sha2-256-96
87
+ hmac-sha1-96
88
+ hmac-ripemd160 hmac-ripemd160@openssh.com
89
+ hmac-md5 hmac-md5-96
90
+ none],
91
+
92
+ compression: %w[none zlib@openssh.com zlib],
93
+ language: %w[]
94
+ }.freeze
95
+
96
+ # The underlying transport layer session that supports this object
97
+ attr_reader :session
98
+
99
+ # The hash of options used to initialize this object
100
+ attr_reader :options
101
+
102
+ # The kex algorithm to use settled on between the client and server.
103
+ attr_reader :kex
104
+
105
+ # The type of host key that will be used for this session.
106
+ attr_reader :host_key
107
+
108
+ # The type of the cipher to use to encrypt packets sent from the client to
109
+ # the server.
110
+ attr_reader :encryption_client
111
+
112
+ # The type of the cipher to use to decrypt packets arriving from the server.
113
+ attr_reader :encryption_server
114
+
115
+ # The type of HMAC to use to sign packets sent by the client.
116
+ attr_reader :hmac_client
117
+
118
+ # The type of HMAC to use to validate packets arriving from the server.
119
+ attr_reader :hmac_server
120
+
121
+ # The type of compression to use to compress packets being sent by the client.
122
+ attr_reader :compression_client
123
+
124
+ # The type of compression to use to decompress packets arriving from the server.
125
+ attr_reader :compression_server
126
+
127
+ # The language that will be used in messages sent by the client.
128
+ attr_reader :language_client
129
+
130
+ # The language that will be used in messages sent from the server.
131
+ attr_reader :language_server
132
+
133
+ # The hash of algorithms preferred by the client, which will be told to
134
+ # the server during algorithm negotiation.
135
+ attr_reader :algorithms
136
+
137
+ # The session-id for this session, as decided during the initial key exchange.
138
+ attr_reader :session_id
139
+
140
+ # Returns true if the given packet can be processed during a key-exchange.
141
+ def self.allowed_packet?(packet)
142
+ (1..4).include?(packet.type) ||
143
+ (6..19).include?(packet.type) ||
144
+ (21..49).include?(packet.type)
145
+ end
146
+
147
+ # Instantiates a new Algorithms object, and prepares the hash of preferred
148
+ # algorithms based on the options parameter and the ALGORITHMS constant.
149
+ def initialize(session, options = {})
150
+ @session = session
151
+ @logger = session.logger
152
+ @options = options
153
+ @algorithms = {}
154
+ @pending = @initialized = false
155
+ @client_packet = @server_packet = nil
156
+ prepare_preferred_algorithms!
157
+ end
158
+
159
+ # Start the algorithm negotation
160
+ def start
161
+ raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
162
+
163
+ send_kexinit
164
+ end
165
+
166
+ # Request a rekey operation. This will return immediately, and does not
167
+ # actually perform the rekey operation. It does cause the session to change
168
+ # state, however--until the key exchange finishes, no new packets will be
169
+ # processed.
170
+ def rekey!
171
+ @client_packet = @server_packet = nil
172
+ @initialized = false
173
+ send_kexinit
174
+ end
175
+
176
+ # Called by the transport layer when a KEXINIT packet is received, indicating
177
+ # that the server wants to exchange keys. This can be spontaneous, or it
178
+ # can be in response to a client-initiated rekey request (see #rekey!). Either
179
+ # way, this will block until the key exchange completes.
180
+ def accept_kexinit(packet)
181
+ info { "got KEXINIT from server" }
182
+ @server_data = parse_server_algorithm_packet(packet)
183
+ @server_packet = @server_data[:raw]
184
+ if !pending?
185
+ send_kexinit
186
+ else
187
+ proceed!
188
+ end
189
+ end
190
+
191
+ # A convenience method for accessing the list of preferred types for a
192
+ # specific algorithm (see #algorithms).
193
+ def [](key)
194
+ algorithms[key]
195
+ end
196
+
197
+ # Returns +true+ if a key-exchange is pending. This will be true from the
198
+ # moment either the client or server requests the key exchange, until the
199
+ # exchange completes. While an exchange is pending, only a limited number
200
+ # of packets are allowed, so event processing essentially stops during this
201
+ # period.
202
+ def pending?
203
+ @pending
204
+ end
205
+
206
+ # Returns true if no exchange is pending, and otherwise returns true or
207
+ # false depending on whether the given packet is of a type that is allowed
208
+ # during a key exchange.
209
+ def allow?(packet)
210
+ !pending? || Algorithms.allowed_packet?(packet)
211
+ end
212
+
213
+ # Returns true if the algorithms have been negotiated at all.
214
+ def initialized?
215
+ @initialized
216
+ end
217
+
218
+ def host_key_format
219
+ case host_key
220
+ when /^([a-z0-9-]+)-cert-v\d{2}@openssh.com$/
221
+ Regexp.last_match[1]
222
+ else
223
+ host_key
224
+ end
225
+ end
226
+
227
+ private
228
+
229
+ # Sends a KEXINIT packet to the server. If a server KEXINIT has already
230
+ # been received, this will then invoke #proceed! to proceed with the key
231
+ # exchange, otherwise it returns immediately (but sets the object to the
232
+ # pending state).
233
+ def send_kexinit
234
+ info { "sending KEXINIT" }
235
+ @pending = true
236
+ packet = build_client_algorithm_packet
237
+ @client_packet = packet.to_s
238
+ session.send_message(packet)
239
+ proceed! if @server_packet
240
+ end
241
+
242
+ # After both client and server have sent their KEXINIT packets, this
243
+ # will do the algorithm negotiation and key exchange. Once both finish,
244
+ # the object leaves the pending state and the method returns.
245
+ def proceed!
246
+ info { "negotiating algorithms" }
247
+ negotiate_algorithms
248
+ exchange_keys
249
+ @pending = false
250
+ end
251
+
252
+ # Prepares the list of preferred algorithms, based on the options hash
253
+ # that was given when the object was constructed, and the ALGORITHMS
254
+ # constant. Also, when determining the host_key type to use, the known
255
+ # hosts files are examined to see if the host has ever sent a host_key
256
+ # before, and if so, that key type is used as the preferred type for
257
+ # communicating with this server.
258
+ def prepare_preferred_algorithms!
259
+ options[:compression] = %w[zlib@openssh.com zlib] if options[:compression] == true
260
+
261
+ ALGORITHMS.each do |algorithm, supported|
262
+ algorithms[algorithm] = compose_algorithm_list(
263
+ supported, options[algorithm] || DEFAULT_ALGORITHMS[algorithm],
264
+ options[:append_all_supported_algorithms]
265
+ )
266
+ end
267
+
268
+ # for convention, make sure our list has the same keys as the server
269
+ # list
270
+
271
+ algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
272
+ algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
273
+ algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
274
+ algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
275
+
276
+ if !options.key?(:host_key)
277
+ # make sure the host keys are specified in preference order, where any
278
+ # existing known key for the host has preference.
279
+
280
+ existing_keys = session.host_keys
281
+ host_keys = existing_keys.flat_map { |key| key.respond_to?(:ssh_types) ? key.ssh_types : [key.ssh_type] }.uniq
282
+ algorithms[:host_key].each do |name|
283
+ host_keys << name unless host_keys.include?(name)
284
+ end
285
+ algorithms[:host_key] = host_keys
286
+ end
287
+ end
288
+
289
+ # Composes the list of algorithms by taking supported algorithms and matching with supplied options.
290
+ def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
291
+ return supported.dup unless option
292
+
293
+ list = []
294
+ option = Array(option).compact.uniq
295
+
296
+ if option.first && option.first.start_with?('+', '-')
297
+ list = supported.dup
298
+
299
+ appends = option.select { |opt| opt.start_with?('+') }.map { |opt| opt[1..-1] }
300
+ deletions = option.select { |opt| opt.start_with?('-') }.map { |opt| opt[1..-1] }
301
+
302
+ list.concat(appends)
303
+
304
+ deletions.each do |opt|
305
+ if opt.include?('*')
306
+ opt_escaped = Regexp.escape(opt)
307
+ algo_re = /\A#{opt_escaped.gsub('\*', '[A-Za-z\d\-@\.]*')}\z/
308
+ list.delete_if { |existing_opt| algo_re.match(existing_opt) }
309
+ else
310
+ list.delete(opt)
311
+ end
312
+ end
313
+
314
+ list.uniq!
315
+ else
316
+ list = option
317
+
318
+ if append_all_supported_algorithms
319
+ supported.each { |name| list << name unless list.include?(name) }
320
+ end
321
+ end
322
+
323
+ unsupported = []
324
+ list.select! do |name|
325
+ is_supported = supported.include?(name)
326
+ unsupported << name unless is_supported
327
+ is_supported
328
+ end
329
+
330
+ lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty?
331
+
332
+ list
333
+ end
334
+
335
+ # Parses a KEXINIT packet from the server.
336
+ def parse_server_algorithm_packet(packet)
337
+ data = { raw: packet.content }
338
+
339
+ packet.read(16) # skip the cookie value
340
+
341
+ data[:kex] = packet.read_string.split(/,/)
342
+ data[:host_key] = packet.read_string.split(/,/)
343
+ data[:encryption_client] = packet.read_string.split(/,/)
344
+ data[:encryption_server] = packet.read_string.split(/,/)
345
+ data[:hmac_client] = packet.read_string.split(/,/)
346
+ data[:hmac_server] = packet.read_string.split(/,/)
347
+ data[:compression_client] = packet.read_string.split(/,/)
348
+ data[:compression_server] = packet.read_string.split(/,/)
349
+ data[:language_client] = packet.read_string.split(/,/)
350
+ data[:language_server] = packet.read_string.split(/,/)
351
+
352
+ # TODO: if first_kex_packet_follows, we need to try to skip the
353
+ # actual kexinit stuff and try to guess what the server is doing...
354
+ # need to read more about this scenario.
355
+ # first_kex_packet_follows = packet.read_bool
356
+
357
+ return data
358
+ end
359
+
360
+ # Given the #algorithms map of preferred algorithm types, this constructs
361
+ # a KEXINIT packet to send to the server. It does not actually send it,
362
+ # it simply builds the packet and returns it.
363
+ def build_client_algorithm_packet
364
+ kex = algorithms[:kex].join(",")
365
+ host_key = algorithms[:host_key].join(",")
366
+ encryption = algorithms[:encryption].join(",")
367
+ hmac = algorithms[:hmac].join(",")
368
+ compression = algorithms[:compression].join(",")
369
+ language = algorithms[:language].join(",")
370
+
371
+ Net::SSH::Buffer.from(:byte, KEXINIT,
372
+ :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
373
+ :mstring, [kex, host_key, encryption, encryption, hmac, hmac],
374
+ :mstring, [compression, compression, language, language],
375
+ :bool, false, :long, 0)
376
+ end
377
+
378
+ # Given the parsed server KEX packet, and the client's preferred algorithm
379
+ # lists in #algorithms, determine which preferred algorithms each has
380
+ # in common and set those as the selected algorithms. If, for any algorithm,
381
+ # no type can be settled on, an exception is raised.
382
+ def negotiate_algorithms
383
+ @kex = negotiate(:kex)
384
+ @host_key = negotiate(:host_key)
385
+ @encryption_client = negotiate(:encryption_client)
386
+ @encryption_server = negotiate(:encryption_server)
387
+ @hmac_client = negotiate(:hmac_client)
388
+ @hmac_server = negotiate(:hmac_server)
389
+ @compression_client = negotiate(:compression_client)
390
+ @compression_server = negotiate(:compression_server)
391
+ @language_client = negotiate(:language_client) rescue ""
392
+ @language_server = negotiate(:language_server) rescue ""
393
+
394
+ debug do
395
+ "negotiated:\n" +
396
+ %i[kex host_key encryption_server encryption_client hmac_client hmac_server
397
+ compression_client compression_server language_client language_server].map do |key|
398
+ "* #{key}: #{instance_variable_get("@#{key}")}"
399
+ end.join("\n")
400
+ end
401
+ end
402
+
403
+ # Negotiates a single algorithm based on the preferences reported by the
404
+ # server and those set by the client. This is called by
405
+ # #negotiate_algorithms.
406
+ def negotiate(algorithm)
407
+ match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
408
+
409
+ if match.nil?
410
+ raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm\n"\
411
+ "Server #{algorithm} preferences: #{@server_data[algorithm].join(',')}\n"\
412
+ "Client #{algorithm} preferences: #{self[algorithm].join(',')}"
413
+ end
414
+
415
+ return match
416
+ end
417
+
418
+ # Considers the sizes of the keys and block-sizes for the selected ciphers,
419
+ # and the lengths of the hmacs, and returns the largest as the byte requirement
420
+ # for the key-exchange algorithm.
421
+ def kex_byte_requirement
422
+ sizes = [8] # require at least 8 bytes
423
+
424
+ sizes.concat(CipherFactory.get_lengths(encryption_client))
425
+ sizes.concat(CipherFactory.get_lengths(encryption_server))
426
+
427
+ sizes << HMAC.key_length(hmac_client)
428
+ sizes << HMAC.key_length(hmac_server)
429
+
430
+ sizes.max
431
+ end
432
+
433
+ # Instantiates one of the Transport::Kex classes (based on the negotiated
434
+ # kex algorithm), and uses it to exchange keys. Then, the ciphers and
435
+ # HMACs are initialized and fed to the transport layer, to be used in
436
+ # further communication with the server.
437
+ def exchange_keys
438
+ debug { "exchanging keys" }
439
+
440
+ algorithm = Kex::MAP[kex].new(self, session,
441
+ client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
442
+ server_version_string: session.server_version.version,
443
+ server_algorithm_packet: @server_packet,
444
+ client_algorithm_packet: @client_packet,
445
+ need_bytes: kex_byte_requirement,
446
+ minimum_dh_bits: options[:minimum_dh_bits],
447
+ logger: logger)
448
+ result = algorithm.exchange_keys
449
+
450
+ secret = result[:shared_secret].to_ssh
451
+ hash = result[:session_id]
452
+ digester = result[:hashing_algorithm]
453
+
454
+ @session_id ||= hash
455
+
456
+ key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
457
+
458
+ iv_client = key["A"]
459
+ iv_server = key["B"]
460
+ key_client = key["C"]
461
+ key_server = key["D"]
462
+ mac_key_client = key["E"]
463
+ mac_key_server = key["F"]
464
+
465
+ parameters = { shared: secret, hash: hash, digester: digester }
466
+
467
+ cipher_client = CipherFactory.get(encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true))
468
+ cipher_server = CipherFactory.get(encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true))
469
+
470
+ mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
471
+ mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
472
+
473
+ session.configure_client cipher: cipher_client, hmac: mac_client,
474
+ compression: normalize_compression_name(compression_client),
475
+ compression_level: options[:compression_level],
476
+ rekey_limit: options[:rekey_limit],
477
+ max_packets: options[:rekey_packet_limit],
478
+ max_blocks: options[:rekey_blocks_limit]
479
+
480
+ session.configure_server cipher: cipher_server, hmac: mac_server,
481
+ compression: normalize_compression_name(compression_server),
482
+ rekey_limit: options[:rekey_limit],
483
+ max_packets: options[:rekey_packet_limit],
484
+ max_blocks: options[:rekey_blocks_limit]
485
+
486
+ @initialized = true
487
+ end
488
+
489
+ # Given the SSH name for some compression algorithm, return a normalized
490
+ # name as a symbol.
491
+ def normalize_compression_name(name)
492
+ case name
493
+ when "none" then false
494
+ when "zlib" then :standard
495
+ when "zlib@openssh.com" then :delayed
496
+ else raise ArgumentError, "unknown compression type `#{name}'"
497
+ end
498
+ end
499
+ end
500
+ end
501
+ end
502
+ end