net-ssh-backports 6.3.0.backports

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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +93 -0
  3. data/.gitignore +13 -0
  4. data/.rubocop.yml +21 -0
  5. data/.rubocop_todo.yml +1074 -0
  6. data/.travis.yml +51 -0
  7. data/CHANGES.txt +698 -0
  8. data/Gemfile +13 -0
  9. data/Gemfile.noed25519 +12 -0
  10. data/ISSUE_TEMPLATE.md +30 -0
  11. data/LICENSE.txt +19 -0
  12. data/Manifest +132 -0
  13. data/README.md +287 -0
  14. data/Rakefile +105 -0
  15. data/THANKS.txt +110 -0
  16. data/appveyor.yml +58 -0
  17. data/lib/net/ssh/authentication/agent.rb +284 -0
  18. data/lib/net/ssh/authentication/certificate.rb +183 -0
  19. data/lib/net/ssh/authentication/constants.rb +20 -0
  20. data/lib/net/ssh/authentication/ed25519.rb +185 -0
  21. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  22. data/lib/net/ssh/authentication/key_manager.rb +297 -0
  23. data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
  24. data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
  25. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
  26. data/lib/net/ssh/authentication/methods/none.rb +34 -0
  27. data/lib/net/ssh/authentication/methods/password.rb +80 -0
  28. data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
  29. data/lib/net/ssh/authentication/pageant.rb +497 -0
  30. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  31. data/lib/net/ssh/authentication/session.rb +163 -0
  32. data/lib/net/ssh/buffer.rb +434 -0
  33. data/lib/net/ssh/buffered_io.rb +202 -0
  34. data/lib/net/ssh/config.rb +406 -0
  35. data/lib/net/ssh/connection/channel.rb +695 -0
  36. data/lib/net/ssh/connection/constants.rb +33 -0
  37. data/lib/net/ssh/connection/event_loop.rb +123 -0
  38. data/lib/net/ssh/connection/keepalive.rb +59 -0
  39. data/lib/net/ssh/connection/session.rb +712 -0
  40. data/lib/net/ssh/connection/term.rb +180 -0
  41. data/lib/net/ssh/errors.rb +106 -0
  42. data/lib/net/ssh/key_factory.rb +218 -0
  43. data/lib/net/ssh/known_hosts.rb +264 -0
  44. data/lib/net/ssh/loggable.rb +62 -0
  45. data/lib/net/ssh/packet.rb +106 -0
  46. data/lib/net/ssh/prompt.rb +62 -0
  47. data/lib/net/ssh/proxy/command.rb +123 -0
  48. data/lib/net/ssh/proxy/errors.rb +16 -0
  49. data/lib/net/ssh/proxy/http.rb +98 -0
  50. data/lib/net/ssh/proxy/https.rb +50 -0
  51. data/lib/net/ssh/proxy/jump.rb +54 -0
  52. data/lib/net/ssh/proxy/socks4.rb +67 -0
  53. data/lib/net/ssh/proxy/socks5.rb +140 -0
  54. data/lib/net/ssh/service/forward.rb +426 -0
  55. data/lib/net/ssh/test/channel.rb +147 -0
  56. data/lib/net/ssh/test/extensions.rb +173 -0
  57. data/lib/net/ssh/test/kex.rb +46 -0
  58. data/lib/net/ssh/test/local_packet.rb +53 -0
  59. data/lib/net/ssh/test/packet.rb +101 -0
  60. data/lib/net/ssh/test/remote_packet.rb +40 -0
  61. data/lib/net/ssh/test/script.rb +180 -0
  62. data/lib/net/ssh/test/socket.rb +65 -0
  63. data/lib/net/ssh/test.rb +94 -0
  64. data/lib/net/ssh/transport/algorithms.rb +502 -0
  65. data/lib/net/ssh/transport/cipher_factory.rb +103 -0
  66. data/lib/net/ssh/transport/constants.rb +40 -0
  67. data/lib/net/ssh/transport/ctr.rb +115 -0
  68. data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
  69. data/lib/net/ssh/transport/hmac/md5.rb +10 -0
  70. data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
  71. data/lib/net/ssh/transport/hmac/none.rb +13 -0
  72. data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
  73. data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
  74. data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
  75. data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
  76. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
  77. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  78. data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
  79. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
  80. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  81. data/lib/net/ssh/transport/hmac.rb +47 -0
  82. data/lib/net/ssh/transport/identity_cipher.rb +57 -0
  83. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  84. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  85. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  86. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  87. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
  88. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  89. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
  90. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
  92. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
  93. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
  94. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
  95. data/lib/net/ssh/transport/kex.rb +31 -0
  96. data/lib/net/ssh/transport/key_expander.rb +30 -0
  97. data/lib/net/ssh/transport/openssl.rb +253 -0
  98. data/lib/net/ssh/transport/packet_stream.rb +280 -0
  99. data/lib/net/ssh/transport/server_version.rb +77 -0
  100. data/lib/net/ssh/transport/session.rb +354 -0
  101. data/lib/net/ssh/transport/state.rb +208 -0
  102. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  103. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  104. data/lib/net/ssh/verifiers/always.rb +58 -0
  105. data/lib/net/ssh/verifiers/never.rb +19 -0
  106. data/lib/net/ssh/version.rb +68 -0
  107. data/lib/net/ssh.rb +330 -0
  108. data/net-ssh-public_cert.pem +20 -0
  109. data/net-ssh.gemspec +44 -0
  110. data/support/ssh_tunnel_bug.rb +65 -0
  111. metadata +271 -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
@@ -0,0 +1,103 @@
1
+ require 'openssl'
2
+ require 'net/ssh/transport/ctr.rb'
3
+ require 'net/ssh/transport/key_expander'
4
+ require 'net/ssh/transport/identity_cipher'
5
+
6
+ module Net
7
+ module SSH
8
+ module Transport
9
+ # Implements a factory of OpenSSL cipher algorithms.
10
+ class CipherFactory
11
+ # Maps the SSH name of a cipher to it's corresponding OpenSSL name
12
+ SSH_TO_OSSL = {
13
+ "3des-cbc" => "des-ede3-cbc",
14
+ "blowfish-cbc" => "bf-cbc",
15
+ "aes256-cbc" => "aes-256-cbc",
16
+ "aes192-cbc" => "aes-192-cbc",
17
+ "aes128-cbc" => "aes-128-cbc",
18
+ "idea-cbc" => "idea-cbc",
19
+ "cast128-cbc" => "cast-cbc",
20
+ "rijndael-cbc@lysator.liu.se" => "aes-256-cbc",
21
+ "3des-ctr" => "des-ede3",
22
+ "blowfish-ctr" => "bf-ecb",
23
+
24
+ "aes256-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-256-ctr") ? "aes-256-ctr" : "aes-256-ecb",
25
+ "aes192-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-192-ctr") ? "aes-192-ctr" : "aes-192-ecb",
26
+ "aes128-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-128-ctr") ? "aes-128-ctr" : "aes-128-ecb",
27
+ 'cast128-ctr' => 'cast5-ecb',
28
+
29
+ 'none' => 'none'
30
+ }
31
+
32
+ # Returns true if the underlying OpenSSL library supports the given cipher,
33
+ # and false otherwise.
34
+ def self.supported?(name)
35
+ ossl_name = SSH_TO_OSSL[name] or raise NotImplementedError, "unimplemented cipher `#{name}'"
36
+ return true if ossl_name == "none"
37
+
38
+ return OpenSSL::Cipher.ciphers.include?(ossl_name)
39
+ end
40
+
41
+ # Retrieves a new instance of the named algorithm. The new instance
42
+ # will be initialized using an iv and key generated from the given
43
+ # iv, key, shared, hash and digester values. Additionally, the
44
+ # cipher will be put into encryption or decryption mode, based on the
45
+ # value of the +encrypt+ parameter.
46
+ def self.get(name, options={})
47
+ ossl_name = SSH_TO_OSSL[name] or raise NotImplementedError, "unimplemented cipher `#{name}'"
48
+ return IdentityCipher if ossl_name == "none"
49
+
50
+ cipher = OpenSSL::Cipher.new(ossl_name)
51
+
52
+ cipher.send(options[:encrypt] ? :encrypt : :decrypt)
53
+
54
+ cipher.padding = 0
55
+
56
+ if name =~ /-ctr(@openssh.org)?$/
57
+ if ossl_name !~ /-ctr/
58
+ cipher.extend(Net::SSH::Transport::CTR)
59
+ else
60
+ cipher = Net::SSH::Transport::OpenSSLAESCTR.new(cipher)
61
+ end
62
+ end
63
+ cipher.iv = Net::SSH::Transport::KeyExpander.expand_key(cipher.iv_len, options[:iv], options)
64
+
65
+ key_len = cipher.key_len
66
+ cipher.key_len = key_len
67
+ cipher.key = Net::SSH::Transport::KeyExpander.expand_key(key_len, options[:key], options)
68
+
69
+ return cipher
70
+ end
71
+
72
+ # Returns a two-element array containing the [ key-length,
73
+ # block-size ] for the named cipher algorithm. If the cipher
74
+ # algorithm is unknown, or is "none", 0 is returned for both elements
75
+ # of the tuple.
76
+ # if :iv_len option is supplied the third return value will be ivlen
77
+ def self.get_lengths(name, options = {})
78
+ ossl_name = SSH_TO_OSSL[name]
79
+ if ossl_name.nil? || ossl_name == "none"
80
+ result = [0, 0]
81
+ result << 0 if options[:iv_len]
82
+ else
83
+ cipher = OpenSSL::Cipher.new(ossl_name)
84
+ key_len = cipher.key_len
85
+ cipher.key_len = key_len
86
+
87
+ block_size =
88
+ case ossl_name
89
+ when /\-ctr/
90
+ Net::SSH::Transport::OpenSSLAESCTR.block_size
91
+ else
92
+ cipher.block_size
93
+ end
94
+
95
+ result = [key_len, block_size]
96
+ result << cipher.iv_len if options[:iv_len]
97
+ end
98
+ result
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end