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.
- data/bin/rb-keygen +210 -0
- data/doc/manual-html/chapter-1.html +2 -2
- data/doc/manual-html/chapter-2.html +13 -2
- data/doc/manual-html/chapter-3.html +2 -2
- data/doc/manual-html/chapter-4.html +2 -2
- data/doc/manual-html/chapter-5.html +2 -2
- data/doc/manual-html/chapter-6.html +2 -2
- data/doc/manual-html/chapter-7.html +2 -2
- data/doc/manual-html/index.html +5 -5
- data/doc/manual/manual.yml +3 -3
- data/doc/manual/parts/0007.txt +2 -0
- data/doc/manual/parts/0008.txt +2 -0
- data/examples/auth-forward.rb +41 -0
- data/lib/net/ssh/connection/driver.rb +15 -1
- data/lib/net/ssh/errors.rb +36 -0
- data/lib/net/ssh/host-key-verifier.rb +108 -0
- data/lib/net/ssh/lenient-host-key-verifier.rb +25 -0
- data/lib/net/ssh/null-host-key-verifier.rb +14 -0
- data/lib/net/ssh/service/agentforward/driver.rb +78 -0
- data/lib/net/ssh/service/agentforward/services.rb +41 -0
- data/lib/net/ssh/service/services.rb +2 -0
- data/lib/net/ssh/session.rb +47 -1
- data/lib/net/ssh/transport/kex/dh.rb +20 -2
- data/lib/net/ssh/transport/kex/services.rb +2 -0
- data/lib/net/ssh/transport/session.rb +10 -0
- data/lib/net/ssh/userauth/agent.rb +11 -0
- data/lib/net/ssh/version.rb +2 -2
- data/test/connection/tc_integration.rb +4 -2
- data/test/service/agentforward/tc_driver.rb +138 -0
- data/test/service/process/tc_integration.rb +2 -0
- data/test/tc_integration.rb +4 -3
- data/test/transport/kex/tc_dh.rb +6 -0
- data/test/transport/kex/tc_dh_gex.rb +1 -0
- data/test/transport/tc_integration.rb +6 -1
- data/test/userauth/tc_integration.rb +2 -0
- metadata +70 -60
data/lib/net/ssh/errors.rb
CHANGED
@@ -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.
|
data/lib/net/ssh/session.rb
CHANGED
@@ -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
|
-
|
161
|
-
|
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
|