net-ssh 2.9.2 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.gitignore +6 -0
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +1129 -0
- data/.travis.yml +41 -5
- data/CHANGES.txt +133 -1
- data/Gemfile +13 -0
- data/Gemfile.norbnacl +10 -0
- data/Gemfile.norbnacl.lock +41 -0
- data/ISSUE_TEMPLATE.md +30 -0
- data/README.rdoc +26 -81
- data/Rakefile +63 -45
- data/appveyor.yml +51 -0
- data/lib/net/ssh/authentication/agent.rb +174 -14
- data/lib/net/ssh/authentication/ed25519.rb +137 -0
- data/lib/net/ssh/authentication/ed25519_loader.rb +21 -0
- data/lib/net/ssh/authentication/key_manager.rb +36 -30
- data/lib/net/ssh/authentication/methods/abstract.rb +4 -0
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +16 -9
- data/lib/net/ssh/authentication/methods/password.rb +17 -4
- data/lib/net/ssh/authentication/pageant.rb +166 -45
- data/lib/net/ssh/authentication/session.rb +3 -2
- data/lib/net/ssh/buffer.rb +49 -10
- data/lib/net/ssh/buffered_io.rb +17 -12
- data/lib/net/ssh/config.rb +39 -8
- data/lib/net/ssh/connection/channel.rb +42 -20
- data/lib/net/ssh/connection/event_loop.rb +114 -0
- data/lib/net/ssh/connection/keepalive.rb +2 -2
- data/lib/net/ssh/connection/session.rb +120 -34
- data/lib/net/ssh/errors.rb +6 -6
- data/lib/net/ssh/key_factory.rb +49 -43
- data/lib/net/ssh/known_hosts.rb +49 -3
- data/lib/net/ssh/prompt.rb +47 -78
- data/lib/net/ssh/proxy/command.rb +31 -5
- data/lib/net/ssh/proxy/http.rb +15 -11
- data/lib/net/ssh/proxy/https.rb +49 -0
- data/lib/net/ssh/proxy/socks4.rb +2 -1
- data/lib/net/ssh/proxy/socks5.rb +3 -2
- data/lib/net/ssh/ruby_compat.rb +2 -29
- data/lib/net/ssh/service/forward.rb +2 -2
- data/lib/net/ssh/test/channel.rb +7 -0
- data/lib/net/ssh/test/extensions.rb +17 -0
- data/lib/net/ssh/test/kex.rb +4 -4
- data/lib/net/ssh/test/packet.rb +18 -2
- data/lib/net/ssh/test/script.rb +16 -2
- data/lib/net/ssh/test/socket.rb +1 -1
- data/lib/net/ssh/test.rb +5 -5
- data/lib/net/ssh/transport/algorithms.rb +92 -75
- data/lib/net/ssh/transport/cipher_factory.rb +19 -26
- data/lib/net/ssh/transport/ctr.rb +7 -9
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +20 -9
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +5 -3
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +1 -1
- data/lib/net/ssh/transport/key_expander.rb +1 -0
- data/lib/net/ssh/transport/openssl.rb +1 -1
- data/lib/net/ssh/transport/packet_stream.rb +11 -3
- data/lib/net/ssh/transport/server_version.rb +13 -6
- data/lib/net/ssh/transport/session.rb +20 -10
- data/lib/net/ssh/transport/state.rb +1 -1
- data/lib/net/ssh/verifiers/secure.rb +8 -10
- data/lib/net/ssh/version.rb +4 -4
- data/lib/net/ssh.rb +62 -14
- data/net-ssh-public_cert.pem +19 -18
- data/net-ssh.gemspec +34 -194
- data/support/arcfour_check.rb +1 -1
- data/support/ssh_tunnel_bug.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +125 -109
- metadata.gz.sig +0 -0
- data/Rudyfile +0 -96
- data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
- data/lib/net/ssh/authentication/agent/socket.rb +0 -178
- data/setup.rb +0 -1585
- data/test/README.txt +0 -47
- data/test/authentication/methods/common.rb +0 -28
- data/test/authentication/methods/test_abstract.rb +0 -51
- data/test/authentication/methods/test_hostbased.rb +0 -114
- data/test/authentication/methods/test_keyboard_interactive.rb +0 -100
- data/test/authentication/methods/test_none.rb +0 -41
- data/test/authentication/methods/test_password.rb +0 -95
- data/test/authentication/methods/test_publickey.rb +0 -148
- data/test/authentication/test_agent.rb +0 -224
- data/test/authentication/test_key_manager.rb +0 -227
- data/test/authentication/test_session.rb +0 -107
- data/test/common.rb +0 -108
- data/test/configs/auth_off +0 -5
- data/test/configs/auth_on +0 -4
- data/test/configs/empty +0 -0
- data/test/configs/eqsign +0 -3
- data/test/configs/exact_match +0 -8
- data/test/configs/host_plus +0 -10
- data/test/configs/multihost +0 -4
- data/test/configs/negative_match +0 -6
- data/test/configs/nohost +0 -19
- data/test/configs/numeric_host +0 -4
- data/test/configs/send_env +0 -2
- data/test/configs/substitutes +0 -8
- data/test/configs/wild_cards +0 -14
- data/test/connection/test_channel.rb +0 -467
- data/test/connection/test_session.rb +0 -543
- data/test/known_hosts/github +0 -1
- data/test/manual/test_forward.rb +0 -285
- data/test/manual/test_pageant.rb +0 -37
- data/test/start/test_connection.rb +0 -53
- data/test/start/test_options.rb +0 -43
- data/test/start/test_transport.rb +0 -28
- data/test/test_all.rb +0 -11
- data/test/test_buffer.rb +0 -433
- data/test/test_buffered_io.rb +0 -63
- data/test/test_config.rb +0 -221
- data/test/test_key_factory.rb +0 -191
- data/test/test_known_hosts.rb +0 -13
- data/test/transport/hmac/test_md5.rb +0 -41
- data/test/transport/hmac/test_md5_96.rb +0 -27
- data/test/transport/hmac/test_none.rb +0 -34
- data/test/transport/hmac/test_ripemd160.rb +0 -36
- data/test/transport/hmac/test_sha1.rb +0 -36
- data/test/transport/hmac/test_sha1_96.rb +0 -27
- data/test/transport/hmac/test_sha2_256.rb +0 -37
- data/test/transport/hmac/test_sha2_256_96.rb +0 -27
- data/test/transport/hmac/test_sha2_512.rb +0 -37
- data/test/transport/hmac/test_sha2_512_96.rb +0 -27
- data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
- data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -146
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -92
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -34
- data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
- data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
- data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
- data/test/transport/test_algorithms.rb +0 -324
- data/test/transport/test_cipher_factory.rb +0 -443
- data/test/transport/test_hmac.rb +0 -34
- data/test/transport/test_identity_cipher.rb +0 -40
- data/test/transport/test_packet_stream.rb +0 -1761
- data/test/transport/test_server_version.rb +0 -78
- data/test/transport/test_session.rb +0 -331
- data/test/transport/test_state.rb +0 -181
data/lib/net/ssh/test/kex.rb
CHANGED
@@ -31,10 +31,10 @@ module Net; module SSH; module Test
|
|
31
31
|
buffer = @connection.next_message
|
32
32
|
raise Net::SSH::Exception, "expected NEWKEYS" unless buffer.type == NEWKEYS
|
33
33
|
|
34
|
-
{ :
|
35
|
-
:
|
36
|
-
:
|
37
|
-
:
|
34
|
+
{ session_id: "abc-xyz",
|
35
|
+
server_key: OpenSSL::PKey::RSA.new(512),
|
36
|
+
shared_secret: OpenSSL::BN.new("1234567890", 10),
|
37
|
+
hashing_algorithm: OpenSSL::Digest::SHA1 }
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
data/lib/net/ssh/test/packet.rb
CHANGED
@@ -17,6 +17,17 @@ module Net; module SSH; module Test
|
|
17
17
|
include Net::SSH::Transport::Constants
|
18
18
|
include Net::SSH::Connection::Constants
|
19
19
|
|
20
|
+
# Register a custom channel request. extra_parts is an array of types
|
21
|
+
# of extra parameters
|
22
|
+
def self.register_channel_request(request, extra_parts)
|
23
|
+
@registered_requests ||= {}
|
24
|
+
@registered_requests[request] = {extra_parts: extra_parts}
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.registered_channel_requests(request)
|
28
|
+
@registered_requests && @registered_requests[request]
|
29
|
+
end
|
30
|
+
|
20
31
|
# Ceate a new packet of the given +type+, and with +args+ being a list of
|
21
32
|
# data elements in the order expected for packets of the given +type+
|
22
33
|
# (see #types).
|
@@ -70,9 +81,14 @@ module Net; module SSH; module Test
|
|
70
81
|
when CHANNEL_REQUEST
|
71
82
|
parts = [:long, :string, :bool]
|
72
83
|
case @data[1]
|
73
|
-
when "exec", "subsystem" then parts << :string
|
84
|
+
when "exec", "subsystem","shell" then parts << :string
|
74
85
|
when "exit-status" then parts << :long
|
75
|
-
|
86
|
+
when "pty-req" then parts.concat([:string, :long, :long, :long, :long, :string])
|
87
|
+
when "env" then parts.contact([:string,:string])
|
88
|
+
else
|
89
|
+
request = Packet.registered_channel_requests(@data[1])
|
90
|
+
raise "don't know what to do about #{@data[1]} channel request" unless request
|
91
|
+
parts.concat(request[:extra_parts])
|
76
92
|
end
|
77
93
|
else raise "don't know how to parse packet type #{@type}"
|
78
94
|
end
|
data/lib/net/ssh/test/script.rb
CHANGED
@@ -63,7 +63,8 @@ module Net; module SSH; module Test
|
|
63
63
|
# indicating whether a response to this packet is required , and +data+
|
64
64
|
# is any additional request-specific data that this packet should send.
|
65
65
|
# +success+ indicates whether the response (if one is required) should be
|
66
|
-
# success or failure.
|
66
|
+
# success or failure. If +data+ is an array it will be treated as multiple
|
67
|
+
# data.
|
67
68
|
#
|
68
69
|
# If a reply is desired, a remote packet will also be queued, :channel_success
|
69
70
|
# if +success+ is true, or :channel_failure if +success+ is false.
|
@@ -71,7 +72,11 @@ module Net; module SSH; module Test
|
|
71
72
|
# This will typically be called via Net::SSH::Test::Channel#sends_exec or
|
72
73
|
# Net::SSH::Test::Channel#sends_subsystem.
|
73
74
|
def sends_channel_request(channel, request, reply, data, success=true)
|
74
|
-
|
75
|
+
if data.is_a? Array
|
76
|
+
events << LocalPacket.new(:channel_request, channel.remote_id, request, reply, *data)
|
77
|
+
else
|
78
|
+
events << LocalPacket.new(:channel_request, channel.remote_id, request, reply, data)
|
79
|
+
end
|
75
80
|
if reply
|
76
81
|
if success
|
77
82
|
events << RemotePacket.new(:channel_success, channel.local_id)
|
@@ -104,6 +109,15 @@ module Net; module SSH; module Test
|
|
104
109
|
events << LocalPacket.new(:channel_close, channel.remote_id)
|
105
110
|
end
|
106
111
|
|
112
|
+
# Scripts the sending of a channel request pty packets from the given
|
113
|
+
# Net::SSH::Test::Channel +channel+. This will typically be called via
|
114
|
+
# Net::SSH::Test::Channel#sends_request_pty.
|
115
|
+
def sends_channel_request_pty(channel)
|
116
|
+
data = ['pty-req', false]
|
117
|
+
data += Net::SSH::Connection::Channel::VALID_PTY_OPTIONS.merge(modes: "\0").values
|
118
|
+
events << LocalPacket.new(:channel_request, channel.remote_id, *data)
|
119
|
+
end
|
120
|
+
|
107
121
|
# Scripts the reception of a channel data packet from the remote host by
|
108
122
|
# the given Net::SSH::Test::Channel +channel+. This will typically be
|
109
123
|
# called via Net::SSH::Test::Channel#gets_data.
|
data/lib/net/ssh/test/socket.rb
CHANGED
@@ -25,8 +25,8 @@ module Net; module SSH; module Test
|
|
25
25
|
|
26
26
|
@script = Script.new
|
27
27
|
|
28
|
-
script.gets(:kexinit, 1, 2, 3, 4, "test", "ssh-rsa", "none", "none", "none", "none", "none", "none", "", "", false)
|
29
28
|
script.sends(:kexinit)
|
29
|
+
script.gets(:kexinit, 1, 2, 3, 4, "test", "ssh-rsa", "none", "none", "none", "none", "none", "none", "", "", false)
|
30
30
|
script.sends(:newkeys)
|
31
31
|
script.gets(:newkeys)
|
32
32
|
end
|
data/lib/net/ssh/test.rb
CHANGED
@@ -10,10 +10,10 @@ module Net; module SSH
|
|
10
10
|
# typically include this module in your unit test class, and then build a
|
11
11
|
# "story" of expected sends and receives:
|
12
12
|
#
|
13
|
-
# require '
|
13
|
+
# require 'minitest/autorun'
|
14
14
|
# require 'net/ssh/test'
|
15
15
|
#
|
16
|
-
# class MyTest < Test
|
16
|
+
# class MyTest < Minitest::Test
|
17
17
|
# include Net::SSH::Test
|
18
18
|
#
|
19
19
|
# def test_exec_via_channel_works
|
@@ -50,7 +50,7 @@ module Net; module SSH
|
|
50
50
|
# If a block is given, yields the script for the test socket (#socket).
|
51
51
|
# Otherwise, simply returns the socket's script. See Net::SSH::Test::Script.
|
52
52
|
def story
|
53
|
-
yield socket.script if block_given?
|
53
|
+
Net::SSH::Test::Extensions::IO.with_test_extension { yield socket.script if block_given? }
|
54
54
|
return socket.script
|
55
55
|
end
|
56
56
|
|
@@ -71,7 +71,7 @@ module Net; module SSH
|
|
71
71
|
# in these tests. It is a fully functional SSH transport session, operating
|
72
72
|
# over a mock socket (#socket).
|
73
73
|
def transport(options={})
|
74
|
-
@transport ||= Net::SSH::Transport::Session.new(options[:host] || "localhost", options.merge(:
|
74
|
+
@transport ||= Net::SSH::Transport::Session.new(options[:host] || "localhost", options.merge(kex: "test", host_key: "ssh-rsa", paranoid: false, proxy: socket(options)))
|
75
75
|
end
|
76
76
|
|
77
77
|
# First asserts that a story has been described (see #story). Then yields,
|
@@ -81,7 +81,7 @@ module Net; module SSH
|
|
81
81
|
# the block passed to this assertion.
|
82
82
|
def assert_scripted
|
83
83
|
raise "there is no script to be processed" if socket.script.events.empty?
|
84
|
-
yield
|
84
|
+
Net::SSH::Test::Extensions::IO.with_test_extension { yield }
|
85
85
|
assert socket.script.events.empty?, "there should not be any remaining scripted events, but there are still #{socket.script.events.length} pending"
|
86
86
|
end
|
87
87
|
end
|
@@ -6,6 +6,7 @@ require 'net/ssh/transport/constants'
|
|
6
6
|
require 'net/ssh/transport/hmac'
|
7
7
|
require 'net/ssh/transport/kex'
|
8
8
|
require 'net/ssh/transport/server_version'
|
9
|
+
require 'net/ssh/authentication/ed25519_loader'
|
9
10
|
|
10
11
|
module Net; module SSH; module Transport
|
11
12
|
|
@@ -22,40 +23,34 @@ module Net; module SSH; module Transport
|
|
22
23
|
# Define the default algorithms, in order of preference, supported by
|
23
24
|
# Net::SSH.
|
24
25
|
ALGORITHMS = {
|
25
|
-
:
|
26
|
-
|
27
|
-
|
28
|
-
:
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
:
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
),
|
46
|
-
|
47
|
-
:hmac => %w(hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96
|
48
|
-
hmac-ripemd160 hmac-ripemd160@openssh.com
|
49
|
-
hmac-sha2-256 hmac-sha2-512 hmac-sha2-256-96
|
50
|
-
hmac-sha2-512-96 none),
|
51
|
-
|
52
|
-
:compression => %w(none zlib@openssh.com zlib),
|
53
|
-
:language => %w()
|
26
|
+
host_key: %w(ssh-rsa ssh-dss
|
27
|
+
ssh-rsa-cert-v01@openssh.com
|
28
|
+
ssh-rsa-cert-v00@openssh.com),
|
29
|
+
kex: %w(diffie-hellman-group-exchange-sha1
|
30
|
+
diffie-hellman-group1-sha1
|
31
|
+
diffie-hellman-group14-sha1
|
32
|
+
diffie-hellman-group-exchange-sha256),
|
33
|
+
encryption: %w(aes128-cbc 3des-cbc blowfish-cbc cast128-cbc
|
34
|
+
aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se
|
35
|
+
idea-cbc none arcfour128 arcfour256 arcfour
|
36
|
+
aes128-ctr aes192-ctr aes256-ctr
|
37
|
+
cast128-ctr blowfish-ctr 3des-ctr),
|
38
|
+
|
39
|
+
hmac: %w(hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96
|
40
|
+
hmac-ripemd160 hmac-ripemd160@openssh.com
|
41
|
+
hmac-sha2-256 hmac-sha2-512 hmac-sha2-256-96
|
42
|
+
hmac-sha2-512-96 none),
|
43
|
+
|
44
|
+
compression: %w(none zlib@openssh.com zlib),
|
45
|
+
language: %w()
|
54
46
|
}
|
55
47
|
if defined?(OpenSSL::PKey::EC)
|
56
48
|
ALGORITHMS[:host_key] += %w(ecdsa-sha2-nistp256
|
57
49
|
ecdsa-sha2-nistp384
|
58
50
|
ecdsa-sha2-nistp521)
|
51
|
+
if Net::SSH::Authentication::ED25519Loader::LOADED
|
52
|
+
ALGORITHMS[:host_key] += %w(ssh-ed25519)
|
53
|
+
end
|
59
54
|
ALGORITHMS[:kex] += %w(ecdh-sha2-nistp256
|
60
55
|
ecdh-sha2-nistp384
|
61
56
|
ecdh-sha2-nistp521)
|
@@ -124,6 +119,12 @@ module Net; module SSH; module Transport
|
|
124
119
|
prepare_preferred_algorithms!
|
125
120
|
end
|
126
121
|
|
122
|
+
# Start the algorithm negotation
|
123
|
+
def start
|
124
|
+
raise ArgumentError, "Cannot call start if it's negoitation started or done" if @pending || @initialized
|
125
|
+
send_kexinit
|
126
|
+
end
|
127
|
+
|
127
128
|
# Request a rekey operation. This will return immediately, and does not
|
128
129
|
# actually perform the rekey operation. It does cause the session to change
|
129
130
|
# state, however--until the key exchange finishes, no new packets will be
|
@@ -210,25 +211,8 @@ module Net; module SSH; module Transport
|
|
210
211
|
def prepare_preferred_algorithms!
|
211
212
|
options[:compression] = %w(zlib@openssh.com zlib) if options[:compression] == true
|
212
213
|
|
213
|
-
ALGORITHMS.each do |algorithm,
|
214
|
-
algorithms[algorithm] =
|
215
|
-
|
216
|
-
# apply the preferred algorithm order, if any
|
217
|
-
if options[algorithm]
|
218
|
-
algorithms[algorithm] = Array(options[algorithm]).compact.uniq
|
219
|
-
unsupported = []
|
220
|
-
algorithms[algorithm].select! do |name|
|
221
|
-
supported = ALGORITHMS[algorithm].include?(name)
|
222
|
-
unsupported << name unless supported
|
223
|
-
supported
|
224
|
-
end
|
225
|
-
lwarn { "unsupported #{algorithm} algorithm: `#{unsupported}'" } unless unsupported.empty?
|
226
|
-
|
227
|
-
# make sure all of our supported algorithms are tacked onto the
|
228
|
-
# end, so that if the user tries to give a list of which none are
|
229
|
-
# supported, we can still proceed.
|
230
|
-
list.each { |name| algorithms[algorithm] << name unless algorithms[algorithm].include?(name) }
|
231
|
-
end
|
214
|
+
ALGORITHMS.each do |algorithm, supported|
|
215
|
+
algorithms[algorithm] = compose_algorithm_list(supported, options[algorithm], options[:append_all_supported_algorithms])
|
232
216
|
end
|
233
217
|
|
234
218
|
# for convention, make sure our list has the same keys as the server
|
@@ -243,7 +227,7 @@ module Net; module SSH; module Transport
|
|
243
227
|
# make sure the host keys are specified in preference order, where any
|
244
228
|
# existing known key for the host has preference.
|
245
229
|
|
246
|
-
existing_keys =
|
230
|
+
existing_keys = session.host_keys
|
247
231
|
host_keys = existing_keys.map { |key| key.ssh_type }.uniq
|
248
232
|
algorithms[:host_key].each do |name|
|
249
233
|
host_keys << name unless host_keys.include?(name)
|
@@ -252,9 +236,41 @@ module Net; module SSH; module Transport
|
|
252
236
|
end
|
253
237
|
end
|
254
238
|
|
239
|
+
# Composes the list of algorithms by taking supported algorithms and matching with supplied options.
|
240
|
+
def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
|
241
|
+
return supported.dup unless option
|
242
|
+
|
243
|
+
list = []
|
244
|
+
option = Array(option).compact.uniq
|
245
|
+
|
246
|
+
if option.first && option.first.start_with?('+')
|
247
|
+
list = supported.dup
|
248
|
+
list << option.first[1..-1]
|
249
|
+
list.concat(option[1..-1])
|
250
|
+
list.uniq!
|
251
|
+
else
|
252
|
+
list = option
|
253
|
+
|
254
|
+
if append_all_supported_algorithms
|
255
|
+
supported.each { |name| list << name unless list.include?(name) }
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
unsupported = []
|
260
|
+
list.select! do |name|
|
261
|
+
is_supported = supported.include?(name)
|
262
|
+
unsupported << name unless is_supported
|
263
|
+
is_supported
|
264
|
+
end
|
265
|
+
|
266
|
+
lwarn { "unsupported #{algorithm} algorithm: `#{unsupported}'" } unless unsupported.empty?
|
267
|
+
|
268
|
+
list
|
269
|
+
end
|
270
|
+
|
255
271
|
# Parses a KEXINIT packet from the server.
|
256
272
|
def parse_server_algorithm_packet(packet)
|
257
|
-
data = { :
|
273
|
+
data = { raw: packet.content }
|
258
274
|
|
259
275
|
packet.read(16) # skip the cookie value
|
260
276
|
|
@@ -290,8 +306,8 @@ module Net; module SSH; module Transport
|
|
290
306
|
|
291
307
|
Net::SSH::Buffer.from(:byte, KEXINIT,
|
292
308
|
:long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
|
293
|
-
:
|
294
|
-
:
|
309
|
+
:mstring, [kex, host_key, encryption, encryption, hmac, hmac],
|
310
|
+
:mstring, [compression, compression, language, language],
|
295
311
|
:bool, false, :long, 0)
|
296
312
|
end
|
297
313
|
|
@@ -355,12 +371,13 @@ module Net; module SSH; module Transport
|
|
355
371
|
debug { "exchanging keys" }
|
356
372
|
|
357
373
|
algorithm = Kex::MAP[kex].new(self, session,
|
358
|
-
:
|
359
|
-
:
|
360
|
-
:
|
361
|
-
:
|
362
|
-
:
|
363
|
-
:
|
374
|
+
client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
|
375
|
+
server_version_string: session.server_version.version,
|
376
|
+
server_algorithm_packet: @server_packet,
|
377
|
+
client_algorithm_packet: @client_packet,
|
378
|
+
need_bytes: kex_byte_requirement,
|
379
|
+
minimum_dh_bits: options[:minimum_dh_bits],
|
380
|
+
logger: logger)
|
364
381
|
result = algorithm.exchange_keys
|
365
382
|
|
366
383
|
secret = result[:shared_secret].to_ssh
|
@@ -370,7 +387,7 @@ module Net; module SSH; module Transport
|
|
370
387
|
@session_id ||= hash
|
371
388
|
|
372
389
|
key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
|
373
|
-
|
390
|
+
|
374
391
|
iv_client = key["A"]
|
375
392
|
iv_server = key["B"]
|
376
393
|
key_client = key["C"]
|
@@ -378,26 +395,26 @@ module Net; module SSH; module Transport
|
|
378
395
|
mac_key_client = key["E"]
|
379
396
|
mac_key_server = key["F"]
|
380
397
|
|
381
|
-
parameters = { :
|
382
|
-
|
383
|
-
cipher_client = CipherFactory.get(encryption_client, parameters.merge(:
|
384
|
-
cipher_server = CipherFactory.get(encryption_server, parameters.merge(:
|
398
|
+
parameters = { shared: secret, hash: hash, digester: digester }
|
399
|
+
|
400
|
+
cipher_client = CipherFactory.get(encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true))
|
401
|
+
cipher_server = CipherFactory.get(encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true))
|
385
402
|
|
386
403
|
mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
|
387
404
|
mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
|
388
405
|
|
389
|
-
session.configure_client :
|
390
|
-
:
|
391
|
-
:
|
392
|
-
:
|
393
|
-
:
|
394
|
-
:
|
395
|
-
|
396
|
-
session.configure_server :
|
397
|
-
:
|
398
|
-
:
|
399
|
-
:
|
400
|
-
:
|
406
|
+
session.configure_client cipher: cipher_client, hmac: mac_client,
|
407
|
+
compression: normalize_compression_name(compression_client),
|
408
|
+
compression_level: options[:compression_level],
|
409
|
+
rekey_limit: options[:rekey_limit],
|
410
|
+
max_packets: options[:rekey_packet_limit],
|
411
|
+
max_blocks: options[:rekey_blocks_limit]
|
412
|
+
|
413
|
+
session.configure_server cipher: cipher_server, hmac: mac_server,
|
414
|
+
compression: normalize_compression_name(compression_server),
|
415
|
+
rekey_limit: options[:rekey_limit],
|
416
|
+
max_packets: options[:rekey_packet_limit],
|
417
|
+
max_blocks: options[:rekey_blocks_limit]
|
401
418
|
|
402
419
|
@initialized = true
|
403
420
|
end
|
@@ -21,12 +21,6 @@ module Net; module SSH; module Transport
|
|
21
21
|
"arcfour256" => "rc4",
|
22
22
|
"arcfour512" => "rc4",
|
23
23
|
"arcfour" => "rc4",
|
24
|
-
"camellia128-cbc" => "camellia-128-cbc",
|
25
|
-
"camellia192-cbc" => "camellia-192-cbc",
|
26
|
-
"camellia256-cbc" => "camellia-256-cbc",
|
27
|
-
"camellia128-cbc@openssh.org" => "camellia-128-cbc",
|
28
|
-
"camellia192-cbc@openssh.org" => "camellia-192-cbc",
|
29
|
-
"camellia256-cbc@openssh.org" => "camellia-256-cbc",
|
30
24
|
|
31
25
|
"3des-ctr" => "des-ede3",
|
32
26
|
"blowfish-ctr" => "bf-ecb",
|
@@ -34,19 +28,13 @@ module Net; module SSH; module Transport
|
|
34
28
|
"aes192-ctr" => "aes-192-ecb",
|
35
29
|
"aes128-ctr" => "aes-128-ecb",
|
36
30
|
"cast128-ctr" => "cast5-ecb",
|
37
|
-
"camellia128-ctr" => "camellia-128-ecb",
|
38
|
-
"camellia192-ctr" => "camellia-192-ecb",
|
39
|
-
"camellia256-ctr" => "camellia-256-ecb",
|
40
|
-
"camellia128-ctr@openssh.org" => "camellia-128-ecb",
|
41
|
-
"camellia192-ctr@openssh.org" => "camellia-192-ecb",
|
42
|
-
"camellia256-ctr@openssh.org" => "camellia-256-ecb",
|
43
31
|
|
44
32
|
"none" => "none",
|
45
33
|
}
|
46
34
|
|
47
35
|
# Ruby's OpenSSL bindings always return a key length of 16 for RC4 ciphers
|
48
|
-
# resulting in the error: OpenSSL::CipherError: key length too short.
|
49
|
-
# The following ciphers will override this key length.
|
36
|
+
# resulting in the error: OpenSSL::CipherError: key length too short.
|
37
|
+
# The following ciphers will override this key length.
|
50
38
|
KEY_LEN_OVERRIDE = {
|
51
39
|
"arcfour256" => 32,
|
52
40
|
"arcfour512" => 64
|
@@ -69,19 +57,18 @@ module Net; module SSH; module Transport
|
|
69
57
|
def self.get(name, options={})
|
70
58
|
ossl_name = SSH_TO_OSSL[name] or raise NotImplementedError, "unimplemented cipher `#{name}'"
|
71
59
|
return IdentityCipher if ossl_name == "none"
|
72
|
-
cipher = OpenSSL::Cipher
|
60
|
+
cipher = OpenSSL::Cipher.new(ossl_name)
|
73
61
|
|
74
62
|
cipher.send(options[:encrypt] ? :encrypt : :decrypt)
|
75
63
|
|
76
64
|
cipher.padding = 0
|
77
65
|
|
78
66
|
cipher.extend(Net::SSH::Transport::CTR) if (name =~ /-ctr(@openssh.org)?$/)
|
79
|
-
|
80
|
-
cipher.iv = Net::SSH::Transport::KeyExpander.expand_key(cipher.iv_len, options[:iv], options) if ossl_name != "rc4"
|
67
|
+
cipher.iv = Net::SSH::Transport::KeyExpander.expand_key(cipher.iv_len, options[:iv], options) if ossl_name != "rc4"
|
81
68
|
|
82
69
|
key_len = KEY_LEN_OVERRIDE[name] || cipher.key_len
|
83
70
|
cipher.key_len = key_len
|
84
|
-
cipher.key
|
71
|
+
cipher.key = Net::SSH::Transport::KeyExpander.expand_key(key_len, options[:key], options)
|
85
72
|
cipher.update(" " * 1536) if (ossl_name == "rc4" && name != "arcfour")
|
86
73
|
|
87
74
|
return cipher
|
@@ -91,15 +78,21 @@ module Net; module SSH; module Transport
|
|
91
78
|
# block-size ] for the named cipher algorithm. If the cipher
|
92
79
|
# algorithm is unknown, or is "none", 0 is returned for both elements
|
93
80
|
# of the tuple.
|
94
|
-
|
81
|
+
# if :iv_len option is supplied the third return value will be ivlen
|
82
|
+
def self.get_lengths(name, options = {})
|
95
83
|
ossl_name = SSH_TO_OSSL[name]
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
84
|
+
if ossl_name.nil? || ossl_name == "none"
|
85
|
+
result = [0, 0]
|
86
|
+
result << 0 if options[:iv_len]
|
87
|
+
else
|
88
|
+
cipher = OpenSSL::Cipher.new(ossl_name)
|
89
|
+
key_len = KEY_LEN_OVERRIDE[name] || cipher.key_len
|
90
|
+
cipher.key_len = key_len
|
91
|
+
|
92
|
+
result = [key_len, ossl_name=="rc4" ? 8 : cipher.block_size]
|
93
|
+
result << cipher.iv_len if options[:iv_len]
|
94
|
+
end
|
95
|
+
result
|
103
96
|
end
|
104
97
|
end
|
105
98
|
|
@@ -12,12 +12,10 @@ module Net::SSH::Transport
|
|
12
12
|
@counter_len = orig.block_size
|
13
13
|
orig.encrypt
|
14
14
|
orig.padding = 0
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
private :_update
|
20
|
-
undef :update
|
15
|
+
|
16
|
+
singleton_class.send(:alias_method, :_update, :update)
|
17
|
+
singleton_class.send(:private, :_update)
|
18
|
+
singleton_class.send(:undef_method, :update)
|
21
19
|
|
22
20
|
def iv
|
23
21
|
@counter
|
@@ -73,13 +71,12 @@ module Net::SSH::Transport
|
|
73
71
|
s
|
74
72
|
end
|
75
73
|
|
76
|
-
private
|
77
|
-
|
78
74
|
def xor!(s1, s2)
|
79
75
|
s = []
|
80
76
|
s1.unpack('Q*').zip(s2.unpack('Q*')) {|a,b| s.push(a^b) }
|
81
77
|
s.pack('Q*')
|
82
78
|
end
|
79
|
+
singleton_class.send(:private, :xor!)
|
83
80
|
|
84
81
|
def increment_counter!
|
85
82
|
c = @counter_len
|
@@ -89,7 +86,8 @@ module Net::SSH::Transport
|
|
89
86
|
end
|
90
87
|
end
|
91
88
|
end
|
92
|
-
|
89
|
+
singleton_class.send(:private, :increment_counter!)
|
90
|
+
}
|
93
91
|
end
|
94
92
|
end
|
95
93
|
end
|
@@ -69,10 +69,10 @@ module Net; module SSH; module Transport; module Kex
|
|
69
69
|
session_id = verify_signature(result)
|
70
70
|
confirm_newkeys
|
71
71
|
|
72
|
-
return { :
|
73
|
-
:
|
74
|
-
:
|
75
|
-
:
|
72
|
+
return { session_id: session_id,
|
73
|
+
server_key: result[:server_key],
|
74
|
+
shared_secret: result[:shared_secret],
|
75
|
+
hashing_algorithm: digester }
|
76
76
|
end
|
77
77
|
|
78
78
|
private
|
@@ -115,11 +115,22 @@ module Net; module SSH; module Transport; module Kex
|
|
115
115
|
def generate_key #:nodoc:
|
116
116
|
dh = OpenSSL::PKey::DH.new
|
117
117
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
118
|
+
if dh.respond_to?(:set_pqg)
|
119
|
+
p, g = get_parameters
|
120
|
+
dh.set_pqg(p, nil, g)
|
121
|
+
else
|
122
|
+
dh.p, dh.g = get_parameters
|
123
|
+
end
|
122
124
|
|
125
|
+
dh.generate_key!
|
126
|
+
until dh.valid? && dh.priv_key.num_bytes == data[:need_bytes]
|
127
|
+
if dh.respond_to?(:set_key)
|
128
|
+
dh.set_key(nil, OpenSSL::BN.rand(data[:need_bytes] * 8))
|
129
|
+
else
|
130
|
+
dh.priv_key = OpenSSL::BN.rand(data[:need_bytes] * 8)
|
131
|
+
end
|
132
|
+
dh.generate_key!
|
133
|
+
end
|
123
134
|
dh
|
124
135
|
end
|
125
136
|
|
@@ -170,7 +181,7 @@ module Net; module SSH; module Transport; module Kex
|
|
170
181
|
|
171
182
|
blob, fingerprint = generate_key_fingerprint(key)
|
172
183
|
|
173
|
-
unless connection.host_key_verifier.verify(:
|
184
|
+
unless connection.host_key_verifier.verify(key: key, key_blob: blob, fingerprint: fingerprint, session: connection)
|
174
185
|
raise Net::SSH::Exception, "host key verification failed"
|
175
186
|
end
|
176
187
|
end
|
@@ -23,8 +23,10 @@ module Net::SSH::Transport::Kex
|
|
23
23
|
# for Compatibility: OpenSSH requires (need_bits * 2 + 1) length of parameter
|
24
24
|
need_bits = data[:need_bytes] * 8 * 2 + 1
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
data[:minimum_dh_bits] ||= MINIMUM_BITS
|
27
|
+
|
28
|
+
if need_bits < data[:minimum_dh_bits]
|
29
|
+
need_bits = data[:minimum_dh_bits]
|
28
30
|
elsif need_bits > MAXIMUM_BITS
|
29
31
|
need_bits = MAXIMUM_BITS
|
30
32
|
end
|
@@ -38,7 +40,7 @@ module Net::SSH::Transport::Kex
|
|
38
40
|
compute_need_bits
|
39
41
|
|
40
42
|
# request the DH key parameters for the given number of bits.
|
41
|
-
buffer = Net::SSH::Buffer.from(:byte, KEXDH_GEX_REQUEST, :long,
|
43
|
+
buffer = Net::SSH::Buffer.from(:byte, KEXDH_GEX_REQUEST, :long, data[:minimum_dh_bits],
|
42
44
|
:long, data[:need_bits], :long, MAXIMUM_BITS)
|
43
45
|
connection.send_message(buffer)
|
44
46
|
|
@@ -57,7 +57,7 @@ module Net; module SSH; module Transport; module Kex
|
|
57
57
|
# send the KEXECDH_INIT message
|
58
58
|
## byte SSH_MSG_KEX_ECDH_INIT
|
59
59
|
## string Q_C, client's ephemeral public key octet string
|
60
|
-
buffer = Net::SSH::Buffer.from(:byte, init, :
|
60
|
+
buffer = Net::SSH::Buffer.from(:byte, init, :mstring, ecdh.public_key.to_bn.to_s(2))
|
61
61
|
connection.send_message(buffer)
|
62
62
|
|
63
63
|
# expect the following KEXECDH_REPLY message
|
@@ -185,7 +185,7 @@ module OpenSSL
|
|
185
185
|
def to_blob
|
186
186
|
@blob ||= Net::SSH::Buffer.from(:string, ssh_type,
|
187
187
|
:string, CurveNameAliasInv[self.group.curve_name],
|
188
|
-
:
|
188
|
+
:mstring, self.public_key.to_bn.to_s(2)).to_s
|
189
189
|
@blob
|
190
190
|
end
|
191
191
|
|
@@ -69,13 +69,13 @@ module Net; module SSH; module Transport
|
|
69
69
|
PROXY_COMMAND_HOST_IP
|
70
70
|
end
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
# Returns true if the IO is available for reading, and false otherwise.
|
74
74
|
def available_for_read?
|
75
75
|
result = Net::SSH::Compat.io_select([self], nil, nil, 0)
|
76
76
|
result && result.first.any?
|
77
77
|
end
|
78
|
-
|
78
|
+
|
79
79
|
# Returns the next full packet. If the mode parameter is :nonblock (the
|
80
80
|
# default), then this will return immediately, whether a packet is
|
81
81
|
# available or not, and will return nil if there is no packet ready to be
|
@@ -84,9 +84,17 @@ module Net; module SSH; module Transport
|
|
84
84
|
def next_packet(mode=:nonblock)
|
85
85
|
case mode
|
86
86
|
when :nonblock then
|
87
|
+
packet = poll_next_packet
|
88
|
+
return packet if packet
|
89
|
+
|
87
90
|
if available_for_read?
|
88
91
|
if fill <= 0
|
89
|
-
|
92
|
+
result = poll_next_packet
|
93
|
+
if result.nil?
|
94
|
+
raise Net::SSH::Disconnect, "connection closed by remote host"
|
95
|
+
else
|
96
|
+
return result
|
97
|
+
end
|
90
98
|
end
|
91
99
|
end
|
92
100
|
poll_next_packet
|