net-ssh 1.0.10 → 1.1.0

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 (36) hide show
  1. data/bin/rb-keygen +210 -0
  2. data/doc/manual-html/chapter-1.html +2 -2
  3. data/doc/manual-html/chapter-2.html +13 -2
  4. data/doc/manual-html/chapter-3.html +2 -2
  5. data/doc/manual-html/chapter-4.html +2 -2
  6. data/doc/manual-html/chapter-5.html +2 -2
  7. data/doc/manual-html/chapter-6.html +2 -2
  8. data/doc/manual-html/chapter-7.html +2 -2
  9. data/doc/manual-html/index.html +5 -5
  10. data/doc/manual/manual.yml +3 -3
  11. data/doc/manual/parts/0007.txt +2 -0
  12. data/doc/manual/parts/0008.txt +2 -0
  13. data/examples/auth-forward.rb +41 -0
  14. data/lib/net/ssh/connection/driver.rb +15 -1
  15. data/lib/net/ssh/errors.rb +36 -0
  16. data/lib/net/ssh/host-key-verifier.rb +108 -0
  17. data/lib/net/ssh/lenient-host-key-verifier.rb +25 -0
  18. data/lib/net/ssh/null-host-key-verifier.rb +14 -0
  19. data/lib/net/ssh/service/agentforward/driver.rb +78 -0
  20. data/lib/net/ssh/service/agentforward/services.rb +41 -0
  21. data/lib/net/ssh/service/services.rb +2 -0
  22. data/lib/net/ssh/session.rb +47 -1
  23. data/lib/net/ssh/transport/kex/dh.rb +20 -2
  24. data/lib/net/ssh/transport/kex/services.rb +2 -0
  25. data/lib/net/ssh/transport/session.rb +10 -0
  26. data/lib/net/ssh/userauth/agent.rb +11 -0
  27. data/lib/net/ssh/version.rb +2 -2
  28. data/test/connection/tc_integration.rb +4 -2
  29. data/test/service/agentforward/tc_driver.rb +138 -0
  30. data/test/service/process/tc_integration.rb +2 -0
  31. data/test/tc_integration.rb +4 -3
  32. data/test/transport/kex/tc_dh.rb +6 -0
  33. data/test/transport/kex/tc_dh_gex.rb +1 -0
  34. data/test/transport/tc_integration.rb +6 -1
  35. data/test/userauth/tc_integration.rb +2 -0
  36. metadata +70 -60
@@ -23,5 +23,41 @@ module Net
23
23
  # Raised when user authentication failed.
24
24
  class AuthenticationFailed < Exception; end
25
25
 
26
+ # Raised when the cached key for a particular host does not match the
27
+ # key given by the host, which can be indicative of a man-in-the-middle
28
+ # attack. When rescuing this exception, you can inspect the key fingerprint
29
+ # and, if you want to proceed anyway, simply call the remember_host!
30
+ # method on the exception, and then retry.
31
+ class HostKeyMismatch < Exception
32
+ attr_writer :callback, :data
33
+
34
+ def [](key)
35
+ @data[key]
36
+ end
37
+
38
+ def fingerprint
39
+ @data[:fingerprint]
40
+ end
41
+
42
+ def host
43
+ @data[:peer][:host]
44
+ end
45
+
46
+ def port
47
+ @data[:peer][:port]
48
+ end
49
+
50
+ def ip
51
+ @data[:peer][:ip]
52
+ end
53
+
54
+ def key
55
+ @data[:key]
56
+ end
57
+
58
+ def remember_host!
59
+ @callback.call
60
+ end
61
+ end
26
62
  end
27
63
  end
@@ -0,0 +1,108 @@
1
+ require 'net/ssh/errors'
2
+
3
+ module Net
4
+ module SSH
5
+
6
+ class HostKeyVerifier
7
+ def verify(arguments)
8
+ # first, find any matches on hostname+port
9
+ matches = keys.select do |item|
10
+ host = item[:host] || arguments[:peer][:host]
11
+ ip = item[:ip] || arguments[:peer][:ip]
12
+ port = item[:port] || arguments[:peer][:port]
13
+
14
+ host == arguments[:peer][:host] &&
15
+ ip == arguments[:peer][:ip] &&
16
+ port == arguments[:peer][:port]
17
+ end
18
+
19
+ # we've never seen this host before, so just automatically add the key.
20
+ # not the most secure option (since the first hit might be the one that
21
+ # is hacked), but since almost nobody actually compares the key
22
+ # fingerprint, this is a reasonable compromise between usability and
23
+ # security.
24
+ if matches.empty?
25
+ add_key(arguments)
26
+ return true
27
+ end
28
+
29
+ # If we found any matches, check to see that the key type and
30
+ # blob also match.
31
+ found = matches.any? do |item|
32
+ item[:type] == arguments[:key].ssh_type &&
33
+ item[:key] == arguments[:key_blob]
34
+ end
35
+
36
+ # If a match was found, return true. Otherwise, raise an exception
37
+ # indicating that the key was not recognized.
38
+ found || process_cache_miss(arguments)
39
+ end
40
+
41
+ private
42
+
43
+ def process_cache_miss(args)
44
+ exception = HostKeyMismatch.new("fingerprint #{args[:fingerprint]} does not match for #{args[:peer][:host]}")
45
+ exception.data = args
46
+ exception.callback = Proc.new { add_key(args) }
47
+ raise exception
48
+ end
49
+
50
+ def home_directory
51
+ ENV['HOME'] ||
52
+ (ENV['HOMEPATH'] && "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}") ||
53
+ "/"
54
+ end
55
+
56
+ def user_key_file
57
+ @user_key_file ||= "#{home_directory}/.ssh/known_hosts"
58
+ end
59
+
60
+ def add_key(args)
61
+ keys << { :host => args[:peer][:host], :port => args[:peer][:port], :ip => args[:peer][:ip], :type => args[:key].ssh_type, :key => args[:key_blob] }
62
+
63
+ key_directory = File.dirname(user_key_file)
64
+ File.mkdir(key_directory, :mode => 0700) if !File.exists?(key_directory)
65
+
66
+ File.open(user_key_file, "a") do |f|
67
+ host = args[:peer][:host]
68
+ host = "[#{host}]:#{args[:peer][:port]}" if host && args[:peer][:port] != 22
69
+
70
+ ip = args[:peer][:ip]
71
+ ip = nil if ip == "127.0.0.1" || ip == "::1"
72
+
73
+ host = [host, ip].compact.join(",")
74
+ f.puts "%s %s %s" % [host, args[:key].ssh_type, [args[:key_blob]].pack("m*").gsub(/\s/, "")]
75
+ end
76
+ end
77
+
78
+ def keys
79
+ @keys ||= begin
80
+ list = []
81
+ list.concat(load_keys_from(user_key_file)) if File.exist?(user_key_file)
82
+ list
83
+ end
84
+ end
85
+
86
+ def load_keys_from(path)
87
+ File.readlines(path).map do |line|
88
+ host, type, key = line.chomp.split
89
+ host, address = host.split(/,/)
90
+
91
+ if address.nil? && host =~ /^\d+\.\d+\.\d+\.\d+$/
92
+ host, address = address, host
93
+ end
94
+
95
+ if host
96
+ host, port = host.split(/:/, 2)
97
+ host = host.gsub(/[\[\]]/, "")
98
+ end
99
+
100
+ key = key.unpack("m*").first
101
+
102
+ { :host => host, :ip => address, :port => port, :type => type, :key => key }
103
+ end
104
+ end
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,25 @@
1
+ require 'net/ssh/host-key-verifier'
2
+
3
+ module Net
4
+ module SSH
5
+
6
+ class LenientHostKeyVerifier < HostKeyVerifier
7
+ def verify(arguments)
8
+ return true if tunnelled?(arguments)
9
+ super
10
+ end
11
+
12
+ private
13
+
14
+ # A connection is potentially being tunnelled if the port is not 22,
15
+ # and the ip refers to the localhost.
16
+ def tunnelled?(args)
17
+ return false if args[:peer][:port].to_i == 22
18
+
19
+ ip = args[:peer][:ip]
20
+ return ip == "127.0.0.1" || ip == "::1"
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ module Net
2
+ module SSH
3
+
4
+ # The NullHostKeyVerifier simply allows every key it sees, without
5
+ # bothering to verify. This mimics the pre-1.1 behavior of Net::SSH, but
6
+ # is not very secure.
7
+ class NullHostKeyVerifier
8
+ def verify(arguments)
9
+ true
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,78 @@
1
+ #--
2
+ # =============================================================================
3
+ # Copyright (c) 2007, Chris Andrews (chris@nodnol.org),
4
+ # Jamis Buck (jgb3@email.byu.edu)
5
+ # All rights reserved.
6
+ #
7
+ # This source file is distributed as part of the Net::SSH Secure Shell Client
8
+ # library for Ruby. This file (and the library as a whole) may be used only as
9
+ # allowed by either the BSD license, or the Ruby license (or, by association
10
+ # with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SSH
11
+ # distribution for the texts of these licenses.
12
+ # -----------------------------------------------------------------------------
13
+ # net-ssh website : http://net-ssh.rubyforge.org
14
+ # project website: http://rubyforge.org/projects/net-ssh
15
+ # =============================================================================
16
+ #++
17
+
18
+ module Net
19
+ module SSH
20
+ module Service
21
+ module AgentForward
22
+
23
+ class Driver
24
+
25
+ def initialize( connection, buffers, log, agent )
26
+ @connection = connection
27
+ @buffers = buffers
28
+ @log = log
29
+ @agent = agent
30
+ @data = ''
31
+
32
+ @connection.add_channel_open_handler(
33
+ "auth-agent@openssh.com", &method(:do_open_channel) )
34
+ end
35
+
36
+ def request
37
+ @connection.channel_request( 'auth-agent-req@openssh.com' )
38
+ end
39
+
40
+ def do_open_channel( connection, channel, data )
41
+ channel.on_data &method(:do_data)
42
+ end
43
+
44
+ # handle CHANNEL_DATA packets received on the agent-forward
45
+ # channel - pass complete received packets to the agent.
46
+ def do_data( channel, data )
47
+ @data = @data + data
48
+ reply = call_agent
49
+ if reply
50
+ channel.send_data reply
51
+ @data = ''
52
+ end
53
+ end
54
+
55
+ # Called if we have any data to forward to the
56
+ # agent. Examines the accumulated data to see if we have a
57
+ # complete packet, based on the length field (the first four
58
+ # bytes as a network long).
59
+ def call_agent
60
+ # if we have enough data to check the length of this packet
61
+ if @data.length >= 4
62
+ packet_length = @data[0..3].unpack('N').first
63
+ # send the complete packet to the agent and read the
64
+ # response
65
+ if @data.length == (4 + packet_length)
66
+ @agent.send_raw_packet @data
67
+ buffer = @agent.read_raw_packet
68
+ end
69
+ end
70
+ buffer
71
+ end
72
+
73
+ end
74
+
75
+ end # AgentForward
76
+ end # Service
77
+ end # SSH
78
+ end # Net
@@ -0,0 +1,41 @@
1
+ #--
2
+ # =============================================================================
3
+ # Copyright (c) 2007, Chris Andrews (chris@nodnol.org),
4
+ # Jamis Buck (jgb3@email.byu.edu)
5
+ # All rights reserved.
6
+ #
7
+ # This source file is distributed as part of the Net::SSH Secure Shell Client
8
+ # library for Ruby. This file (and the library as a whole) may be used only as
9
+ # allowed by either the BSD license, or the Ruby license (or, by association
10
+ # with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SSH
11
+ # distribution for the texts of these licenses.
12
+ # -----------------------------------------------------------------------------
13
+ # net-ssh website : http://net-ssh.rubyforge.org
14
+ # project website: http://rubyforge.org/projects/net-ssh
15
+ # =============================================================================
16
+ #++
17
+
18
+ module Net
19
+ module SSH
20
+ module Service
21
+ module AgentForward
22
+
23
+ def register_services( container )
24
+
25
+ container.namespace_define :agentforward do |ns|
26
+ ns.driver :model => :singleton_deferred do |c,p|
27
+ require 'net/ssh/service/agentforward/driver'
28
+ Driver.new( c[:connection][:driver],
29
+ c[:transport][:buffers],
30
+ c[:log_for, p],
31
+ c[:userauth].agent)
32
+ end
33
+ end
34
+
35
+ end
36
+ module_function :register_services
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -34,12 +34,14 @@ module Net
34
34
  ns.require "net/ssh/service/forward/services", "#{self}::Forward"
35
35
  ns.require "net/ssh/service/process/services", "#{self}::Process"
36
36
  ns.require "net/ssh/service/shell/services", "#{self}::Shell"
37
+ ns.require "net/ssh/service/agentforward/services", "#{self}::AgentForward"
37
38
  end
38
39
 
39
40
  # Add the services to the services hash.
40
41
  container.services[ :forward ] = container.service.forward.driver
41
42
  container.services[ :process ] = container.service.process.driver
42
43
  container.services[ :shell ] = container.service.shell.driver
44
+ container.services[ :agentforward ] = container.service.agentforward.driver
43
45
 
44
46
  # Register the external services and add them to the collection of
45
47
  # known services.
@@ -82,6 +82,13 @@ module Net
82
82
  # :warn).
83
83
  # * :log (the name of the file, or the IO object, to which messages will
84
84
  # be logged. Defaults to STDERR.)
85
+ # * :forward_agent (true or false, whether or not to forward requests
86
+ # for the authentication agent. Defaults to false.)
87
+ # * :paranoid (either false, in which case server fingerprints are not
88
+ # verified, true, in which case they are verified and mismatches result
89
+ # in a warning and a prompt, or an object responding to :allow?, which
90
+ # will be invoked and should return true or false for whether or not
91
+ # to allow the connection. Defaults to true.)
85
92
  #
86
93
  # Also, any options recognized by Net::SSH::Transport::Session may be
87
94
  # given, and will be passed through to initialize the transport session.
@@ -92,6 +99,8 @@ module Net
92
99
  # returned (and must be closed explicitly).
93
100
  def initialize( *args )
94
101
  @open = false
102
+ @agent_forwarded = false
103
+
95
104
  process_arguments( *args )
96
105
 
97
106
  @registry.define do |b|
@@ -103,6 +112,8 @@ module Net
103
112
  b.userauth_host_keys { @host_keys }
104
113
  b.userauth_method_order { @auth_methods }
105
114
 
115
+ b.host_key_verifier { @host_key_verifier }
116
+
106
117
  # Register myself with the registry, so that other services may
107
118
  # access me.
108
119
  b.session( :pipeline => [] ) { self }
@@ -152,6 +163,17 @@ module Net
152
163
  sanity_check
153
164
  channel = @connection.open_channel( type, data )
154
165
  channel.on_confirm_open(&block)
166
+
167
+ # If we have an agent, and agent-forwarding is enabled, set up
168
+ # the forwarding. Do this once only, after the first channel
169
+ # is opened.
170
+ if @forward_agent && @registry[:userauth].agent
171
+ unless @agent_forwarded
172
+ agentforward.request
173
+ @agent_forwarded = true
174
+ end
175
+ end
176
+
155
177
  channel
156
178
  end
157
179
 
@@ -218,7 +240,9 @@ module Net
218
240
  @keys = @options[ :keys ]
219
241
  @host_keys = @options[ :host_keys ]
220
242
  @auth_methods = @options[ :auth_methods ]
243
+ @forward_agent = @options[ :forward_agent ] || false
221
244
  @crypto_backend = @options.fetch( :crypto_backend, :ossl )
245
+ @host_key_verifier = host_key_verifier_from(@options.fetch(:paranoid, true))
222
246
 
223
247
  verbose = @options.fetch( :verbose, :warn )
224
248
  log = @options.fetch( :log, STDERR )
@@ -238,7 +262,8 @@ module Net
238
262
  Needle::Registry.new( @registry_options )
239
263
 
240
264
  [ :keys, :host_keys, :auth_methods, :username, :password,
241
- :crypto_backend, :registry_options, :container, :log, :verbose
265
+ :crypto_backend, :registry_options, :container, :log, :verbose,
266
+ :forward_agent, :paranoid
242
267
  ].each do |i|
243
268
  @options.delete i
244
269
  end
@@ -247,6 +272,27 @@ module Net
247
272
  end
248
273
  private :process_arguments
249
274
 
275
+ def host_key_verifier_from(paranoia)
276
+ case paranoia
277
+ when true then
278
+ require 'net/ssh/lenient-host-key-verifier'
279
+ Net::SSH::LenientHostKeyVerifier.new
280
+ when false then
281
+ require 'net/ssh/null-host-key-verifier'
282
+ Net::SSH::NullHostKeyVerifier.new
283
+ when :very then
284
+ require 'net/ssh/host-key-verifier'
285
+ Net::SS::HostKeyVerifier.new
286
+ else
287
+ if paranoia.respond_to?(:verify)
288
+ paranoia
289
+ else
290
+ raise ArgumentError, "argument to :paranoid is not valid: #{paranoia.inspect}"
291
+ end
292
+ end
293
+ end
294
+ private :host_key_verifier_from
295
+
250
296
  # Make sure we're in an acceptible state.
251
297
  def sanity_check
252
298
  raise Net::SSH::Exception, "session not open" unless @open
@@ -49,6 +49,9 @@ module Net
49
49
  # The reference to the buffer factory to use.
50
50
  attr_writer :buffers
51
51
 
52
+ # The reference to the host key verifier to use to verify host keys.
53
+ attr_writer :host_key_verifier
54
+
52
55
  # Create a new instance of the DiffieHellmanGroup1SHA1 algorithm.
53
56
  # The parameters are, respectively, a factory for creating new
54
57
  # Bignum instances, and a factory for obtaining digester objects.
@@ -157,8 +160,23 @@ module Net
157
160
  "'#{key.ssh_type}' != '#{session.algorithms.host_key}'"
158
161
  end
159
162
 
160
- # TODO: verify that the server key is really the key for the given
161
- # host, probably by having a service that can verify server keys.
163
+ blob, fingerprint = generate_key_fingerprint(key)
164
+
165
+ unless @host_key_verifier.verify(:key => key, :key_blob => blob, :fingerprint => fingerprint, :peer => session.peer)
166
+ raise Net::SSH::Exception, "host key verification failed"
167
+ end
168
+ end
169
+
170
+ def generate_key_fingerprint(key)
171
+ writer = @buffers.writer
172
+ writer.write_key(key)
173
+
174
+ blob = writer.to_s
175
+ fingerprint = OpenSSL::Digest::MD5.hexdigest(blob).scan(/../).join(":")
176
+
177
+ [blob, fingerprint]
178
+ rescue ::Exception => e
179
+ [nil, "(could not generate fingerprint: #{e.message})"]
162
180
  end
163
181
 
164
182
  # Verify the signature that was received. Raise Net::SSH::Exception