net-ssh-net-ssh 2.0.12

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