ddollar-net-ssh 2.0.1

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 (103) hide show
  1. data/CHANGELOG.rdoc +42 -0
  2. data/Manifest +101 -0
  3. data/README.rdoc +110 -0
  4. data/Rakefile +26 -0
  5. data/THANKS.rdoc +16 -0
  6. data/lib/net/ssh.rb +199 -0
  7. data/lib/net/ssh/authentication/agent.rb +175 -0
  8. data/lib/net/ssh/authentication/constants.rb +18 -0
  9. data/lib/net/ssh/authentication/key_manager.rb +169 -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 +176 -0
  16. data/lib/net/ssh/authentication/session.rb +127 -0
  17. data/lib/net/ssh/buffer.rb +339 -0
  18. data/lib/net/ssh/buffered_io.rb +149 -0
  19. data/lib/net/ssh/config.rb +173 -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 +569 -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 +85 -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 +128 -0
  34. data/lib/net/ssh/service/forward.rb +267 -0
  35. data/lib/net/ssh/test.rb +89 -0
  36. data/lib/net/ssh/test/channel.rb +129 -0
  37. data/lib/net/ssh/test/extensions.rb +152 -0
  38. data/lib/net/ssh/test/kex.rb +44 -0
  39. data/lib/net/ssh/test/local_packet.rb +51 -0
  40. data/lib/net/ssh/test/packet.rb +81 -0
  41. data/lib/net/ssh/test/remote_packet.rb +38 -0
  42. data/lib/net/ssh/test/script.rb +157 -0
  43. data/lib/net/ssh/test/socket.rb +59 -0
  44. data/lib/net/ssh/transport/algorithms.rb +384 -0
  45. data/lib/net/ssh/transport/cipher_factory.rb +72 -0
  46. data/lib/net/ssh/transport/constants.rb +30 -0
  47. data/lib/net/ssh/transport/hmac.rb +31 -0
  48. data/lib/net/ssh/transport/hmac/abstract.rb +48 -0
  49. data/lib/net/ssh/transport/hmac/md5.rb +12 -0
  50. data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  51. data/lib/net/ssh/transport/hmac/none.rb +15 -0
  52. data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  53. data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  54. data/lib/net/ssh/transport/identity_cipher.rb +40 -0
  55. data/lib/net/ssh/transport/kex.rb +13 -0
  56. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
  57. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
  58. data/lib/net/ssh/transport/openssl.rb +128 -0
  59. data/lib/net/ssh/transport/packet_stream.rb +230 -0
  60. data/lib/net/ssh/transport/server_version.rb +61 -0
  61. data/lib/net/ssh/transport/session.rb +262 -0
  62. data/lib/net/ssh/transport/state.rb +170 -0
  63. data/lib/net/ssh/verifiers/lenient.rb +30 -0
  64. data/lib/net/ssh/verifiers/null.rb +12 -0
  65. data/lib/net/ssh/verifiers/strict.rb +53 -0
  66. data/lib/net/ssh/version.rb +60 -0
  67. data/net-ssh.gemspec +56 -0
  68. data/setup.rb +1585 -0
  69. data/test/authentication/methods/common.rb +28 -0
  70. data/test/authentication/methods/test_abstract.rb +51 -0
  71. data/test/authentication/methods/test_hostbased.rb +108 -0
  72. data/test/authentication/methods/test_keyboard_interactive.rb +98 -0
  73. data/test/authentication/methods/test_password.rb +50 -0
  74. data/test/authentication/methods/test_publickey.rb +123 -0
  75. data/test/authentication/test_agent.rb +205 -0
  76. data/test/authentication/test_key_manager.rb +100 -0
  77. data/test/authentication/test_session.rb +93 -0
  78. data/test/common.rb +106 -0
  79. data/test/configs/exact_match +8 -0
  80. data/test/configs/wild_cards +14 -0
  81. data/test/connection/test_channel.rb +452 -0
  82. data/test/connection/test_session.rb +483 -0
  83. data/test/test_all.rb +6 -0
  84. data/test/test_buffer.rb +336 -0
  85. data/test/test_buffered_io.rb +63 -0
  86. data/test/test_config.rb +78 -0
  87. data/test/test_key_factory.rb +67 -0
  88. data/test/transport/hmac/test_md5.rb +34 -0
  89. data/test/transport/hmac/test_md5_96.rb +25 -0
  90. data/test/transport/hmac/test_none.rb +34 -0
  91. data/test/transport/hmac/test_sha1.rb +34 -0
  92. data/test/transport/hmac/test_sha1_96.rb +25 -0
  93. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
  94. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
  95. data/test/transport/test_algorithms.rb +302 -0
  96. data/test/transport/test_cipher_factory.rb +163 -0
  97. data/test/transport/test_hmac.rb +34 -0
  98. data/test/transport/test_identity_cipher.rb +40 -0
  99. data/test/transport/test_packet_stream.rb +433 -0
  100. data/test/transport/test_server_version.rb +55 -0
  101. data/test/transport/test_session.rb +312 -0
  102. data/test/transport/test_state.rb +173 -0
  103. metadata +222 -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),
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,72 @@
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
+ "none" => "none"
19
+ }
20
+
21
+ # Retrieves a new instance of the named algorithm. The new instance
22
+ # will be initialized using an iv and key generated from the given
23
+ # iv, key, shared, hash and digester values. Additionally, the
24
+ # cipher will be put into encryption or decryption mode, based on the
25
+ # value of the +encrypt+ parameter.
26
+ def self.get(name, options={})
27
+ ossl_name = SSH_TO_OSSL[name] or raise NotImplementedError, "unimplemented cipher `#{name}'"
28
+ return IdentityCipher if ossl_name == "none"
29
+
30
+ cipher = OpenSSL::Cipher::Cipher.new(ossl_name)
31
+ cipher.send(options[:encrypt] ? :encrypt : :decrypt)
32
+
33
+ cipher.padding = 0
34
+ cipher.iv = make_key(cipher.iv_len, options[:iv], options)
35
+ cipher.key = make_key(cipher.key_len, options[:key], options)
36
+
37
+ return cipher
38
+ end
39
+
40
+ # Returns a two-element array containing the [ key-length,
41
+ # block-size ] for the named cipher algorithm. If the cipher
42
+ # algorithm is unknown, or is "none", 0 is returned for both elements
43
+ # of the tuple.
44
+ def self.get_lengths(name)
45
+ ossl_name = SSH_TO_OSSL[name]
46
+ return [0, 0] if ossl_name.nil? || ossl_name == "none"
47
+
48
+ cipher = OpenSSL::Cipher::Cipher.new(ossl_name)
49
+ return [cipher.key_len, cipher.block_size]
50
+ end
51
+
52
+ private
53
+
54
+ # Generate a key value in accordance with the SSH2 specification.
55
+ def self.make_key(bytes, start, options={})
56
+ k = start[0, bytes]
57
+
58
+ digester = options[:digester]
59
+ shared = options[:shared]
60
+ hash = options[:hash]
61
+
62
+ while k.length < bytes
63
+ step = digester.digest(shared + hash + k)
64
+ bytes_needed = bytes - k.length
65
+ k << step[0, bytes_needed]
66
+ end
67
+
68
+ return k
69
+ end
70
+ end
71
+
72
+ end; end; end