net-ssh 4.1.0 → 6.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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +8 -2
- data/.rubocop_todo.yml +405 -552
- data/.travis.yml +23 -22
- data/CHANGES.txt +112 -1
- data/Gemfile +1 -7
- data/{Gemfile.norbnacl → Gemfile.noed25519} +1 -1
- data/Manifest +4 -5
- data/README.md +287 -0
- data/Rakefile +40 -29
- data/appveyor.yml +12 -6
- data/lib/net/ssh.rb +68 -32
- data/lib/net/ssh/authentication/agent.rb +234 -222
- data/lib/net/ssh/authentication/certificate.rb +175 -164
- data/lib/net/ssh/authentication/constants.rb +17 -14
- data/lib/net/ssh/authentication/ed25519.rb +162 -141
- data/lib/net/ssh/authentication/ed25519_loader.rb +32 -29
- data/lib/net/ssh/authentication/key_manager.rb +40 -9
- data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
- data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +1 -1
- data/lib/net/ssh/authentication/methods/none.rb +10 -10
- data/lib/net/ssh/authentication/methods/password.rb +13 -13
- data/lib/net/ssh/authentication/methods/publickey.rb +56 -55
- data/lib/net/ssh/authentication/pageant.rb +468 -465
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
- data/lib/net/ssh/authentication/session.rb +130 -122
- data/lib/net/ssh/buffer.rb +345 -312
- data/lib/net/ssh/buffered_io.rb +163 -163
- data/lib/net/ssh/config.rb +316 -238
- data/lib/net/ssh/connection/channel.rb +670 -650
- data/lib/net/ssh/connection/constants.rb +30 -26
- data/lib/net/ssh/connection/event_loop.rb +108 -105
- data/lib/net/ssh/connection/keepalive.rb +54 -50
- data/lib/net/ssh/connection/session.rb +682 -671
- data/lib/net/ssh/connection/term.rb +180 -176
- data/lib/net/ssh/errors.rb +101 -99
- data/lib/net/ssh/key_factory.rb +195 -108
- data/lib/net/ssh/known_hosts.rb +161 -152
- data/lib/net/ssh/loggable.rb +57 -55
- data/lib/net/ssh/packet.rb +82 -78
- data/lib/net/ssh/prompt.rb +55 -53
- data/lib/net/ssh/proxy/command.rb +104 -89
- data/lib/net/ssh/proxy/errors.rb +12 -8
- data/lib/net/ssh/proxy/http.rb +93 -91
- data/lib/net/ssh/proxy/https.rb +42 -39
- data/lib/net/ssh/proxy/jump.rb +50 -47
- data/lib/net/ssh/proxy/socks4.rb +0 -2
- data/lib/net/ssh/proxy/socks5.rb +11 -12
- data/lib/net/ssh/service/forward.rb +370 -317
- data/lib/net/ssh/test.rb +83 -77
- data/lib/net/ssh/test/channel.rb +146 -142
- data/lib/net/ssh/test/extensions.rb +150 -146
- data/lib/net/ssh/test/kex.rb +35 -31
- data/lib/net/ssh/test/local_packet.rb +48 -44
- data/lib/net/ssh/test/packet.rb +87 -84
- data/lib/net/ssh/test/remote_packet.rb +35 -31
- data/lib/net/ssh/test/script.rb +173 -171
- data/lib/net/ssh/test/socket.rb +59 -55
- data/lib/net/ssh/transport/algorithms.rb +430 -364
- data/lib/net/ssh/transport/cipher_factory.rb +95 -91
- data/lib/net/ssh/transport/constants.rb +33 -25
- data/lib/net/ssh/transport/ctr.rb +33 -11
- data/lib/net/ssh/transport/hmac.rb +15 -13
- data/lib/net/ssh/transport/hmac/abstract.rb +82 -63
- data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/identity_cipher.rb +55 -51
- data/lib/net/ssh/transport/kex.rb +14 -13
- data/lib/net/ssh/transport/kex/abstract.rb +123 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +112 -217
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -62
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
- data/lib/net/ssh/transport/key_expander.rb +29 -25
- data/lib/net/ssh/transport/openssl.rb +116 -116
- data/lib/net/ssh/transport/packet_stream.rb +223 -190
- data/lib/net/ssh/transport/server_version.rb +64 -66
- data/lib/net/ssh/transport/session.rb +306 -257
- data/lib/net/ssh/transport/state.rb +198 -196
- data/lib/net/ssh/verifiers/accept_new.rb +35 -0
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +34 -0
- data/lib/net/ssh/verifiers/always.rb +56 -0
- data/lib/net/ssh/verifiers/never.rb +21 -0
- data/lib/net/ssh/version.rb +55 -53
- data/net-ssh-public_cert.pem +18 -19
- data/net-ssh.gemspec +12 -11
- data/support/ssh_tunnel_bug.rb +2 -2
- metadata +86 -75
- metadata.gz.sig +0 -0
- data/Gemfile.norbnacl.lock +0 -41
- data/README.rdoc +0 -169
- data/lib/net/ssh/ruby_compat.rb +0 -24
- data/lib/net/ssh/verifiers/lenient.rb +0 -30
- data/lib/net/ssh/verifiers/null.rb +0 -12
- data/lib/net/ssh/verifiers/secure.rb +0 -52
- data/lib/net/ssh/verifiers/strict.rb +0 -24
- data/support/arcfour_check.rb +0 -20
data/Rakefile
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: UTF-8
|
2
1
|
#
|
3
2
|
# Also in your terminal environment run:
|
4
3
|
# $ export LANG=en_US.UTF-8
|
@@ -12,7 +11,6 @@ require "bundler/gem_tasks"
|
|
12
11
|
|
13
12
|
require "rdoc/task"
|
14
13
|
|
15
|
-
|
16
14
|
desc "When releasing make sure NET_SSH_BUILDGEM_SIGNED is set"
|
17
15
|
task :check_NET_SSH_BUILDGEM_SIGNED do
|
18
16
|
raise "NET_SSH_BUILDGEM_SIGNED should be set to release" unless ENV['NET_SSH_BUILDGEM_SIGNED']
|
@@ -21,9 +19,8 @@ end
|
|
21
19
|
Rake::Task[:release].enhance [:check_NET_SSH_BUILDGEM_SIGNED]
|
22
20
|
Rake::Task[:release].prerequisites.unshift(:check_NET_SSH_BUILDGEM_SIGNED)
|
23
21
|
|
24
|
-
|
25
22
|
task default: ["build"]
|
26
|
-
CLEAN.include [
|
23
|
+
CLEAN.include ['pkg', 'rdoc']
|
27
24
|
name = "net-ssh"
|
28
25
|
|
29
26
|
require_relative "lib/net/ssh/version"
|
@@ -34,38 +31,52 @@ RDoc::Task.new do |rdoc|
|
|
34
31
|
rdoc.rdoc_dir = "rdoc"
|
35
32
|
rdoc.title = "#{name} #{version}"
|
36
33
|
rdoc.generator = 'hanna' # gem install hanna-nouveau
|
37
|
-
rdoc.main = 'README.
|
34
|
+
rdoc.main = 'README.md'
|
38
35
|
rdoc.rdoc_files.include("README*")
|
39
36
|
rdoc.rdoc_files.include("bin/*.rb")
|
40
37
|
rdoc.rdoc_files.include("lib/**/*.rb")
|
41
38
|
extra_files.each { |file|
|
42
|
-
rdoc.rdoc_files.include(file) if File.
|
39
|
+
rdoc.rdoc_files.include(file) if File.exist?(file)
|
43
40
|
}
|
44
41
|
end
|
45
42
|
|
46
|
-
namespace :
|
47
|
-
desc "Update
|
48
|
-
task :
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
sh "
|
56
|
-
|
57
|
-
end
|
58
|
-
# update
|
59
|
-
sh "cp -rf ./rdoc/* /tmp/net-ssh-gh-pages/net-ssh/"
|
60
|
-
Dir.chdir "/tmp/net-ssh-gh-pages/net-ssh" do
|
61
|
-
sh "git add -A ."
|
62
|
-
sh "git commit -m \"Update docs\""
|
63
|
-
end
|
64
|
-
# publish
|
65
|
-
Dir.chdir "/tmp/net-ssh-gh-pages/net-ssh" do
|
66
|
-
sh "git push origin gh-pages"
|
43
|
+
namespace :cert do
|
44
|
+
desc "Update public cert from private - only run if public is expired"
|
45
|
+
task :update_public_when_expired do
|
46
|
+
require 'openssl'
|
47
|
+
require 'time'
|
48
|
+
raw = File.read "net-ssh-public_cert.pem"
|
49
|
+
certificate = OpenSSL::X509::Certificate.new raw
|
50
|
+
raise Exception, "Not yet expired: #{certificate.not_after}" unless certificate.not_after < Time.now
|
51
|
+
sh "gem cert --build netssh@solutious.com --days 365*5 --private-key /mnt/gem/net-ssh-private_key.pem"
|
52
|
+
sh "mv gem-public_cert.pem net-ssh-public_cert.pem"
|
53
|
+
sh "gem cert --add net-ssh-public_cert.pem"
|
67
54
|
end
|
68
55
|
end
|
56
|
+
|
57
|
+
namespace :rdoc do
|
58
|
+
desc "Update gh-pages branch"
|
59
|
+
task :publish do
|
60
|
+
# copy/checkout
|
61
|
+
rm_rf "/tmp/net-ssh-rdoc"
|
62
|
+
rm_rf "/tmp/net-ssh-gh-pages"
|
63
|
+
cp_r "./rdoc", "/tmp/net-ssh-rdoc"
|
64
|
+
mkdir "/tmp/net-ssh-gh-pages"
|
65
|
+
Dir.chdir "/tmp/net-ssh-gh-pages" do
|
66
|
+
sh "git clone --branch gh-pages --single-branch https://github.com/net-ssh/net-ssh"
|
67
|
+
rm_rf "/tmp/net-ssh-gh-pages/net-ssh/*"
|
68
|
+
end
|
69
|
+
# update
|
70
|
+
sh "cp -rf ./rdoc/* /tmp/net-ssh-gh-pages/net-ssh/"
|
71
|
+
Dir.chdir "/tmp/net-ssh-gh-pages/net-ssh" do
|
72
|
+
sh "git add -A ."
|
73
|
+
sh "git commit -m \"Update docs\""
|
74
|
+
end
|
75
|
+
# publish
|
76
|
+
Dir.chdir "/tmp/net-ssh-gh-pages/net-ssh" do
|
77
|
+
sh "git push origin gh-pages"
|
78
|
+
end
|
79
|
+
end
|
69
80
|
end
|
70
81
|
|
71
82
|
require 'rake/testtask'
|
@@ -80,7 +91,7 @@ Rake::TestTask.new do |t|
|
|
80
91
|
test_files -= FileList['test/manual/test_*.rb']
|
81
92
|
test_files -= FileList['test/test_pageant.rb']
|
82
93
|
test_files -= FileList['test/test/**/test_*.rb']
|
83
|
-
t.test_files =
|
94
|
+
t.test_files = test_files
|
84
95
|
end
|
85
96
|
|
86
97
|
desc "Run tests of Net::SSH:Test"
|
@@ -89,5 +100,5 @@ Rake::TestTask.new do |t|
|
|
89
100
|
# we need to run test/test separatedly as it hacks io + other modules
|
90
101
|
t.libs = ["lib", "test"]
|
91
102
|
test_files = FileList['test/test/**/test_*.rb']
|
92
|
-
t.test_files =
|
103
|
+
t.test_files = test_files
|
93
104
|
end
|
data/appveyor.yml
CHANGED
@@ -5,9 +5,15 @@ skip_tags: true
|
|
5
5
|
environment:
|
6
6
|
matrix:
|
7
7
|
- ruby_version: "jruby-9.1.2.0"
|
8
|
+
- ruby_version: "26-x64"
|
9
|
+
- ruby_version: "25-x64"
|
10
|
+
- ruby_version: "24-x64"
|
8
11
|
- ruby_version: "23"
|
9
12
|
- ruby_version: "23-x64"
|
10
|
-
|
13
|
+
|
14
|
+
matrix:
|
15
|
+
allow_failures:
|
16
|
+
- ruby_version: "jruby-9.1.2.0"
|
11
17
|
|
12
18
|
#init:
|
13
19
|
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
@@ -25,9 +31,9 @@ install:
|
|
25
31
|
- if "%ruby_version%" == "jruby-9.1.2.0" ( cinst jruby --version 9.1.2.0 -i --allow-empty-checksums )
|
26
32
|
- if "%ruby_version%" == "jruby-9.1.2.0" ( SET "PATH=C:\jruby-9.1.2.0\bin\;%PATH%" )
|
27
33
|
- ruby --version
|
28
|
-
- gem install bundler --no-document -v 1.
|
29
|
-
- SET BUNDLE_GEMFILE=Gemfile.
|
30
|
-
- bundle
|
34
|
+
- gem install bundler --no-document --user-install -v 1.17
|
35
|
+
- SET BUNDLE_GEMFILE=Gemfile.noed25519
|
36
|
+
- bundle install --retry=3
|
31
37
|
- cinst freesshd
|
32
38
|
- cinst putty --allow-empty-checksums
|
33
39
|
- ps: |
|
@@ -45,8 +51,8 @@ install:
|
|
45
51
|
}
|
46
52
|
|
47
53
|
test_script:
|
48
|
-
- SET BUNDLE_GEMFILE=Gemfile.
|
54
|
+
- SET BUNDLE_GEMFILE=Gemfile.noed25519
|
49
55
|
- SET NET_SSH_RUN_WIN_INTEGRATION_TESTS=YES
|
50
|
-
- bundle
|
56
|
+
- bundle exec rake test
|
51
57
|
|
52
58
|
build: off
|
data/lib/net/ssh.rb
CHANGED
@@ -4,6 +4,7 @@ ENV['HOME'] ||= ENV['HOMEPATH'] ? "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" : Dir.
|
|
4
4
|
|
5
5
|
require 'logger'
|
6
6
|
require 'etc'
|
7
|
+
require 'shellwords'
|
7
8
|
|
8
9
|
require 'net/ssh/config'
|
9
10
|
require 'net/ssh/errors'
|
@@ -62,17 +63,18 @@ module Net
|
|
62
63
|
module SSH
|
63
64
|
# This is the set of options that Net::SSH.start recognizes. See
|
64
65
|
# Net::SSH.start for a description of each option.
|
65
|
-
VALID_OPTIONS = [
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
66
|
+
VALID_OPTIONS = %i[
|
67
|
+
auth_methods bind_address compression compression_level config
|
68
|
+
encryption forward_agent hmac host_key remote_user
|
69
|
+
keepalive keepalive_interval keepalive_maxcount kex keys key_data
|
70
|
+
keycerts languages logger paranoid password port proxy
|
71
|
+
rekey_blocks_limit rekey_limit rekey_packet_limit timeout verbose
|
72
|
+
known_hosts global_known_hosts_file user_known_hosts_file host_key_alias
|
73
|
+
host_name user properties passphrase keys_only max_pkt_size
|
74
|
+
max_win_size send_env set_env use_agent number_of_password_prompts
|
75
|
+
append_all_supported_algorithms non_interactive password_prompt
|
76
|
+
agent_socket_factory minimum_dh_bits verify_host_key
|
77
|
+
fingerprint_hash check_host_ip
|
76
78
|
]
|
77
79
|
|
78
80
|
# The standard means of starting a new SSH connection. When used with a
|
@@ -107,6 +109,8 @@ module Net
|
|
107
109
|
# * :bind_address => the IP address on the connecting machine to use in
|
108
110
|
# establishing connection. (:bind_address is discarded if :proxy
|
109
111
|
# is set.)
|
112
|
+
# * :check_host_ip => Also ckeck IP address when connecting to remote host.
|
113
|
+
# Defaults to +true+.
|
110
114
|
# * :compression => the compression algorithm to use, or +true+ to use
|
111
115
|
# whatever is supported.
|
112
116
|
# * :compression_level => the compression level to use when sending data
|
@@ -141,6 +145,8 @@ module Net
|
|
141
145
|
# * :kex => the key exchange algorithm (or algorithms) to use
|
142
146
|
# * :keys => an array of file names of private keys to use for publickey
|
143
147
|
# and hostbased authentication
|
148
|
+
# * :keycerts => an array of file names of key certificates to use
|
149
|
+
# with publickey authentication
|
144
150
|
# * :key_data => an array of strings, with each element of the array being
|
145
151
|
# a raw private key in PEM format.
|
146
152
|
# * :keys_only => set to +true+ to use only private keys from +keys+ and
|
@@ -157,12 +163,7 @@ module Net
|
|
157
163
|
# authentication failure vs password prompt. Non-interactive applications
|
158
164
|
# should set it to true to prefer failing a password/etc auth methods vs.
|
159
165
|
# asking for password.
|
160
|
-
# * :paranoid =>
|
161
|
-
# strict host-key verification should be (in increasing order here).
|
162
|
-
# You can also provide an own Object which responds to +verify+. The argument
|
163
|
-
# given to +verify+ is a hash consisting of the +:key+, the +:key_blob+,
|
164
|
-
# the +:fingerprint+ and the +:session+. Returning true accepts the host key,
|
165
|
-
# returning false declines it and closes the connection.
|
166
|
+
# * :paranoid => deprecated alias for :verify_host_key
|
166
167
|
# * :passphrase => the passphrase to use when loading a private key (default
|
167
168
|
# is +nil+, for no passphrase)
|
168
169
|
# * :password => the password to use to login
|
@@ -175,6 +176,8 @@ module Net
|
|
175
176
|
# * :rekey_packet_limit => the max number of packets to process before rekeying
|
176
177
|
# * :send_env => an array of local environment variable names to export to the
|
177
178
|
# remote environment. Names may be given as String or Regexp.
|
179
|
+
# * :set_env => a hash of environment variable names and values to set to the
|
180
|
+
# remote environment. Override the ones if specified in +send_env+.
|
178
181
|
# * :timeout => how long to wait for the initial connection to be made
|
179
182
|
# * :user => the user name to log in as; this overrides the +user+
|
180
183
|
# parameter, and is primarily only useful when provided via an SSH
|
@@ -197,8 +200,19 @@ module Net
|
|
197
200
|
# * :password_prompt => a custom prompt object with ask method. See Net::SSH::Prompt
|
198
201
|
#
|
199
202
|
# * :agent_socket_factory => enables the user to pass a lambda/block that will serve as the socket factory
|
200
|
-
# Net::SSH
|
203
|
+
# Net::SSH.start(host,user,agent_socket_factory: ->{ UNIXSocket.open('/foo/bar') })
|
201
204
|
# example: ->{ UNIXSocket.open('/foo/bar')}
|
205
|
+
# * :verify_host_key => specify how strict host-key verification should be.
|
206
|
+
# In order of increasing strictness:
|
207
|
+
# * :never (very insecure) ::Net::SSH::Verifiers::Never
|
208
|
+
# * :accept_new_or_local_tunnel (insecure) ::Net::SSH::Verifiers::AcceptNewOrLocalTunnel
|
209
|
+
# * :accept_new (insecure) ::Net::SSH::Verifiers::AcceptNew
|
210
|
+
# * :always (secure) ::Net::SSH::Verifiers::Always
|
211
|
+
# You can also provide an own Object which responds to +verify+. The argument
|
212
|
+
# given to +verify+ is a hash consisting of the +:key+, the +:key_blob+,
|
213
|
+
# the +:fingerprint+ and the +:session+. Returning true accepts the host key,
|
214
|
+
# returning false declines it and closes the connection.
|
215
|
+
# * :fingerprint_hash => 'MD5' or 'SHA256', defaults to 'SHA256'
|
202
216
|
# If +user+ parameter is nil it defaults to USER from ssh_config, or
|
203
217
|
# local username
|
204
218
|
def self.start(host, user=nil, options={}, &block)
|
@@ -214,26 +228,30 @@ module Net
|
|
214
228
|
options = configuration_for(host, options.fetch(:config, true)).merge(options)
|
215
229
|
host = options.fetch(:host_name, host)
|
216
230
|
|
231
|
+
options[:check_host_ip] = true unless options.key?(:check_host_ip)
|
232
|
+
|
217
233
|
if options[:non_interactive]
|
218
234
|
options[:number_of_password_prompts] = 0
|
219
235
|
end
|
220
236
|
|
237
|
+
_support_deprecated_option_paranoid(options)
|
238
|
+
|
221
239
|
if options[:verbose]
|
222
240
|
options[:logger].level = case options[:verbose]
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
241
|
+
when Integer then options[:verbose]
|
242
|
+
when :debug then Logger::DEBUG
|
243
|
+
when :info then Logger::INFO
|
244
|
+
when :warn then Logger::WARN
|
245
|
+
when :error then Logger::ERROR
|
246
|
+
when :fatal then Logger::FATAL
|
247
|
+
else raise ArgumentError, "can't convert #{options[:verbose].inspect} to any of the Logger level constants"
|
248
|
+
end
|
231
249
|
end
|
232
250
|
|
233
251
|
transport = Transport::Session.new(host, options)
|
234
252
|
auth = Authentication::Session.new(transport, options)
|
235
253
|
|
236
|
-
user = options.fetch(:user, user) || Etc.
|
254
|
+
user = options.fetch(:user, user) || Etc.getpwuid.name
|
237
255
|
if auth.authenticate("ssh-connection", user, options[:password])
|
238
256
|
connection = Connection::Session.new(transport, options)
|
239
257
|
if block_given?
|
@@ -262,10 +280,10 @@ module Net
|
|
262
280
|
# See Net::SSH::Config for the full description of all supported options.
|
263
281
|
def self.configuration_for(host, use_ssh_config)
|
264
282
|
files = case use_ssh_config
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
283
|
+
when true then Net::SSH::Config.expandable_default_files
|
284
|
+
when false, nil then return {}
|
285
|
+
else Array(use_ssh_config)
|
286
|
+
end
|
269
287
|
|
270
288
|
Net::SSH::Config.for(host, files)
|
271
289
|
end
|
@@ -278,7 +296,7 @@ module Net
|
|
278
296
|
|
279
297
|
options[:password_prompt] ||= Prompt.default(options)
|
280
298
|
|
281
|
-
[
|
299
|
+
%i[password passphrase].each do |key|
|
282
300
|
options.delete(key) if options.key?(key) && options[key].nil?
|
283
301
|
end
|
284
302
|
end
|
@@ -291,5 +309,23 @@ module Net
|
|
291
309
|
end
|
292
310
|
end
|
293
311
|
private_class_method :_sanitize_options
|
312
|
+
|
313
|
+
def self._support_deprecated_option_paranoid(options)
|
314
|
+
if options.key?(:paranoid)
|
315
|
+
Kernel.warn(
|
316
|
+
":paranoid is deprecated, please use :verify_host_key. Supported " \
|
317
|
+
"values are exactly the same, only the name of the option has changed."
|
318
|
+
)
|
319
|
+
if options.key?(:verify_host_key)
|
320
|
+
Kernel.warn(
|
321
|
+
"Both :paranoid and :verify_host_key were specified. " \
|
322
|
+
":verify_host_key takes precedence, :paranoid will be ignored."
|
323
|
+
)
|
324
|
+
else
|
325
|
+
options[:verify_host_key] = options.delete(:paranoid)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
private_class_method :_support_deprecated_option_paranoid
|
294
330
|
end
|
295
331
|
end
|
@@ -8,249 +8,261 @@ require 'rubygems'
|
|
8
8
|
|
9
9
|
require 'net/ssh/authentication/pageant' if Gem.win_platform? && RUBY_PLATFORM != "java"
|
10
10
|
|
11
|
-
module Net
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
SSH2_AGENT_REQUEST_IDENTITIES = 11
|
34
|
-
SSH2_AGENT_IDENTITIES_ANSWER = 12
|
35
|
-
SSH2_AGENT_SIGN_REQUEST = 13
|
36
|
-
SSH2_AGENT_SIGN_RESPONSE = 14
|
37
|
-
SSH2_AGENT_ADD_IDENTITY = 17
|
38
|
-
SSH2_AGENT_REMOVE_IDENTITY = 18
|
39
|
-
SSH2_AGENT_REMOVE_ALL_IDENTITIES = 19
|
40
|
-
SSH2_AGENT_ADD_ID_CONSTRAINED = 25
|
41
|
-
SSH2_AGENT_FAILURE = 30
|
42
|
-
SSH2_AGENT_VERSION_RESPONSE = 103
|
43
|
-
|
44
|
-
SSH_COM_AGENT2_FAILURE = 102
|
45
|
-
|
46
|
-
SSH_AGENT_REQUEST_RSA_IDENTITIES = 1
|
47
|
-
SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2
|
48
|
-
SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5
|
49
|
-
SSH_AGENT_FAILURE = 5
|
50
|
-
SSH_AGENT_SUCCESS = 6
|
51
|
-
|
52
|
-
SSH_AGENT_CONSTRAIN_LIFETIME = 1
|
53
|
-
SSH_AGENT_CONSTRAIN_CONFIRM = 2
|
54
|
-
|
55
|
-
# The underlying socket being used to communicate with the SSH agent.
|
56
|
-
attr_reader :socket
|
57
|
-
|
58
|
-
# Instantiates a new agent object, connects to a running SSH agent,
|
59
|
-
# negotiates the agent protocol version, and returns the agent object.
|
60
|
-
def self.connect(logger=nil, agent_socket_factory = nil)
|
61
|
-
agent = new(logger)
|
62
|
-
agent.connect!(agent_socket_factory)
|
63
|
-
agent.negotiate!
|
64
|
-
agent
|
65
|
-
end
|
66
|
-
|
67
|
-
# Creates a new Agent object, using the optional logger instance to
|
68
|
-
# report status.
|
69
|
-
def initialize(logger=nil)
|
70
|
-
self.logger = logger
|
71
|
-
end
|
11
|
+
module Net
|
12
|
+
module SSH
|
13
|
+
module Authentication
|
14
|
+
# Class for representing agent-specific errors.
|
15
|
+
class AgentError < Net::SSH::Exception; end
|
16
|
+
# An exception for indicating that the SSH agent is not available.
|
17
|
+
class AgentNotAvailable < AgentError; end
|
18
|
+
|
19
|
+
# This class implements a simple client for the ssh-agent protocol. It
|
20
|
+
# does not implement any specific protocol, but instead copies the
|
21
|
+
# behavior of the ssh-agent functions in the OpenSSH library (3.8).
|
22
|
+
#
|
23
|
+
# This means that although it behaves like a SSH1 client, it also has
|
24
|
+
# some SSH2 functionality (like signing data).
|
25
|
+
class Agent
|
26
|
+
include Loggable
|
27
|
+
|
28
|
+
# A simple module for extending keys, to allow comments to be specified
|
29
|
+
# for them.
|
30
|
+
module Comment
|
31
|
+
attr_accessor :comment
|
32
|
+
end
|
72
33
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
34
|
+
SSH2_AGENT_REQUEST_VERSION = 1
|
35
|
+
SSH2_AGENT_REQUEST_IDENTITIES = 11
|
36
|
+
SSH2_AGENT_IDENTITIES_ANSWER = 12
|
37
|
+
SSH2_AGENT_SIGN_REQUEST = 13
|
38
|
+
SSH2_AGENT_SIGN_RESPONSE = 14
|
39
|
+
SSH2_AGENT_ADD_IDENTITY = 17
|
40
|
+
SSH2_AGENT_REMOVE_IDENTITY = 18
|
41
|
+
SSH2_AGENT_REMOVE_ALL_IDENTITIES = 19
|
42
|
+
SSH2_AGENT_ADD_ID_CONSTRAINED = 25
|
43
|
+
SSH2_AGENT_FAILURE = 30
|
44
|
+
SSH2_AGENT_VERSION_RESPONSE = 103
|
45
|
+
|
46
|
+
SSH_COM_AGENT2_FAILURE = 102
|
47
|
+
|
48
|
+
SSH_AGENT_REQUEST_RSA_IDENTITIES = 1
|
49
|
+
SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2
|
50
|
+
SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5
|
51
|
+
SSH_AGENT_FAILURE = 5
|
52
|
+
SSH_AGENT_SUCCESS = 6
|
53
|
+
|
54
|
+
SSH_AGENT_CONSTRAIN_LIFETIME = 1
|
55
|
+
SSH_AGENT_CONSTRAIN_CONFIRM = 2
|
56
|
+
|
57
|
+
SSH_AGENT_RSA_SHA2_256 = 0x02
|
58
|
+
SSH_AGENT_RSA_SHA2_512 = 0x04
|
59
|
+
|
60
|
+
# The underlying socket being used to communicate with the SSH agent.
|
61
|
+
attr_reader :socket
|
62
|
+
|
63
|
+
# Instantiates a new agent object, connects to a running SSH agent,
|
64
|
+
# negotiates the agent protocol version, and returns the agent object.
|
65
|
+
def self.connect(logger=nil, agent_socket_factory = nil, identity_agent = nil)
|
66
|
+
agent = new(logger)
|
67
|
+
agent.connect!(agent_socket_factory, identity_agent)
|
68
|
+
agent.negotiate!
|
69
|
+
agent
|
88
70
|
end
|
89
|
-
rescue StandardError => e
|
90
|
-
error { "could not connect to ssh-agent: #{e.message}" }
|
91
|
-
raise AgentNotAvailable, $!.message
|
92
|
-
end
|
93
71
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
raise AgentNotAvailable, "SSH2 agents are not yet supported" if type == SSH2_AGENT_VERSION_RESPONSE
|
101
|
-
if type == SSH2_AGENT_FAILURE
|
102
|
-
debug { "Unexpected response type==#{type}, this will be ignored" }
|
103
|
-
elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
|
104
|
-
raise AgentNotAvailable, "unknown response from agent: #{type}, #{body.to_s.inspect}"
|
105
|
-
end
|
106
|
-
end
|
72
|
+
# Creates a new Agent object, using the optional logger instance to
|
73
|
+
# report status.
|
74
|
+
def initialize(logger=nil)
|
75
|
+
self.logger = logger
|
76
|
+
end
|
107
77
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
78
|
+
# Connect to the agent process using the socket factory and socket name
|
79
|
+
# given by the attribute writers. If the agent on the other end of the
|
80
|
+
# socket reports that it is an SSH2-compatible agent, this will fail
|
81
|
+
# (it only supports the ssh-agent distributed by OpenSSH).
|
82
|
+
def connect!(agent_socket_factory = nil, identity_agent = nil)
|
83
|
+
debug { "connecting to ssh-agent" }
|
84
|
+
@socket =
|
85
|
+
if agent_socket_factory
|
86
|
+
agent_socket_factory.call
|
87
|
+
elsif identity_agent
|
88
|
+
unix_socket_class.open(identity_agent)
|
89
|
+
elsif ENV['SSH_AUTH_SOCK'] && unix_socket_class
|
90
|
+
unix_socket_class.open(ENV['SSH_AUTH_SOCK'])
|
91
|
+
elsif Gem.win_platform? && RUBY_ENGINE != "jruby"
|
92
|
+
Pageant::Socket.open
|
93
|
+
else
|
94
|
+
raise AgentNotAvailable, "Agent not configured"
|
95
|
+
end
|
96
|
+
rescue StandardError => e
|
97
|
+
error { "could not connect to ssh-agent: #{e.message}" }
|
98
|
+
raise AgentNotAvailable, $!.message
|
127
99
|
end
|
128
|
-
end
|
129
100
|
|
130
|
-
|
131
|
-
|
101
|
+
# Attempts to negotiate the SSH agent protocol version. Raises an error
|
102
|
+
# if the version could not be negotiated successfully.
|
103
|
+
def negotiate!
|
104
|
+
# determine what type of agent we're communicating with
|
105
|
+
type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)
|
106
|
+
|
107
|
+
raise AgentNotAvailable, "SSH2 agents are not yet supported" if type == SSH2_AGENT_VERSION_RESPONSE
|
108
|
+
if type == SSH2_AGENT_FAILURE
|
109
|
+
debug { "Unexpected response type==#{type}, this will be ignored" }
|
110
|
+
elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
|
111
|
+
raise AgentNotAvailable, "unknown response from agent: #{type}, #{body.to_s.inspect}"
|
112
|
+
end
|
113
|
+
end
|
132
114
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
115
|
+
# Return an array of all identities (public keys) known to the agent.
|
116
|
+
# Each key returned is augmented with a +comment+ property which is set
|
117
|
+
# to the comment returned by the agent for that key.
|
118
|
+
def identities
|
119
|
+
type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
|
120
|
+
raise AgentError, "could not get identity count" if agent_failed(type)
|
121
|
+
raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
|
122
|
+
|
123
|
+
identities = []
|
124
|
+
body.read_long.times do
|
125
|
+
key_str = body.read_string
|
126
|
+
comment_str = body.read_string
|
127
|
+
begin
|
128
|
+
key = Buffer.new(key_str).read_key
|
129
|
+
if key.nil?
|
130
|
+
error { "ignoring invalid key: #{comment_str}" }
|
131
|
+
next
|
132
|
+
end
|
133
|
+
key.extend(Comment)
|
134
|
+
key.comment = comment_str
|
135
|
+
identities.push key
|
136
|
+
rescue NotImplementedError => e
|
137
|
+
error { "ignoring unimplemented key:#{e.message} #{comment_str}" }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
return identities
|
142
|
+
end
|
138
143
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
144
|
+
# Closes this socket. This agent reference is no longer able to
|
145
|
+
# query the agent.
|
146
|
+
def close
|
147
|
+
@socket.close
|
148
|
+
end
|
143
149
|
|
144
|
-
|
145
|
-
|
150
|
+
# Using the agent and the given public key, sign the given data. The
|
151
|
+
# signature is returned in SSH2 format.
|
152
|
+
def sign(key, data, flags = 0)
|
153
|
+
type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, flags)
|
146
154
|
|
147
|
-
|
148
|
-
|
155
|
+
raise AgentError, "agent could not sign data with requested identity" if agent_failed(type)
|
156
|
+
raise AgentError, "bad authentication response #{type}" if type != SSH2_AGENT_SIGN_RESPONSE
|
149
157
|
|
150
|
-
|
151
|
-
|
152
|
-
# seconds.
|
153
|
-
# If confirm is true, confirmation will be required for each agent signing
|
154
|
-
# operation.
|
155
|
-
def add_identity(priv_key, comment, lifetime: nil, confirm: false)
|
156
|
-
constraints = Buffer.new
|
157
|
-
if lifetime
|
158
|
-
constraints.write_byte(SSH_AGENT_CONSTRAIN_LIFETIME)
|
159
|
-
constraints.write_long(lifetime)
|
160
|
-
end
|
161
|
-
constraints.write_byte(SSH_AGENT_CONSTRAIN_CONFIRM) if confirm
|
158
|
+
return reply.read_string
|
159
|
+
end
|
162
160
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
161
|
+
# Adds the private key with comment to the agent.
|
162
|
+
# If lifetime is given, the key will automatically be removed after lifetime
|
163
|
+
# seconds.
|
164
|
+
# If confirm is true, confirmation will be required for each agent signing
|
165
|
+
# operation.
|
166
|
+
def add_identity(priv_key, comment, lifetime: nil, confirm: false)
|
167
|
+
constraints = Buffer.new
|
168
|
+
if lifetime
|
169
|
+
constraints.write_byte(SSH_AGENT_CONSTRAIN_LIFETIME)
|
170
|
+
constraints.write_long(lifetime)
|
171
|
+
end
|
172
|
+
constraints.write_byte(SSH_AGENT_CONSTRAIN_CONFIRM) if confirm
|
173
|
+
|
174
|
+
req_type = constraints.empty? ? SSH2_AGENT_ADD_IDENTITY : SSH2_AGENT_ADD_ID_CONSTRAINED
|
175
|
+
type, = send_and_wait(req_type, :string, priv_key.ssh_type, :raw, blob_for_add(priv_key),
|
176
|
+
:string, comment, :raw, constraints)
|
177
|
+
raise AgentError, "could not add identity to agent" if type != SSH_AGENT_SUCCESS
|
178
|
+
end
|
168
179
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
180
|
+
# Removes key from the agent.
|
181
|
+
def remove_identity(key)
|
182
|
+
type, = send_and_wait(SSH2_AGENT_REMOVE_IDENTITY, :string, key.to_blob)
|
183
|
+
raise AgentError, "could not remove identity from agent" if type != SSH_AGENT_SUCCESS
|
184
|
+
end
|
174
185
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
186
|
+
# Removes all identities from the agent.
|
187
|
+
def remove_all_identities
|
188
|
+
type, = send_and_wait(SSH2_AGENT_REMOVE_ALL_IDENTITIES)
|
189
|
+
raise AgentError, "could not remove all identity from agent" if type != SSH_AGENT_SUCCESS
|
190
|
+
end
|
180
191
|
|
181
|
-
|
192
|
+
private
|
182
193
|
|
183
|
-
|
184
|
-
|
185
|
-
|
194
|
+
def unix_socket_class
|
195
|
+
defined?(UNIXSocket) && UNIXSocket
|
196
|
+
end
|
186
197
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
198
|
+
# Send a new packet of the given type, with the associated data.
|
199
|
+
def send_packet(type, *args)
|
200
|
+
buffer = Buffer.from(*args)
|
201
|
+
data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
|
202
|
+
debug { "sending agent request #{type} len #{buffer.length}" }
|
203
|
+
@socket.send data, 0
|
204
|
+
end
|
194
205
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
206
|
+
# Read the next packet from the agent. This will return a two-part
|
207
|
+
# tuple consisting of the packet type, and the packet's body (which
|
208
|
+
# is returned as a Net::SSH::Buffer).
|
209
|
+
def read_packet
|
210
|
+
buffer = Net::SSH::Buffer.new(@socket.read(4))
|
211
|
+
buffer.append(@socket.read(buffer.read_long))
|
212
|
+
type = buffer.read_byte
|
213
|
+
debug { "received agent packet #{type} len #{buffer.length - 4}" }
|
214
|
+
return type, buffer
|
215
|
+
end
|
205
216
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
217
|
+
# Send the given packet and return the subsequent reply from the agent.
|
218
|
+
# (See #send_packet and #read_packet).
|
219
|
+
def send_and_wait(type, *args)
|
220
|
+
send_packet(type, *args)
|
221
|
+
read_packet
|
222
|
+
end
|
212
223
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
224
|
+
# Returns +true+ if the parameter indicates a "failure" response from
|
225
|
+
# the agent, and +false+ otherwise.
|
226
|
+
def agent_failed(type)
|
227
|
+
type == SSH_AGENT_FAILURE ||
|
228
|
+
type == SSH2_AGENT_FAILURE ||
|
229
|
+
type == SSH_COM_AGENT2_FAILURE
|
230
|
+
end
|
220
231
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
232
|
+
def blob_for_add(priv_key)
|
233
|
+
# Ideally we'd have something like `to_private_blob` on the various key types, but the
|
234
|
+
# nuances with encoding (e.g. `n` and `e` are reversed for RSA keys) make this impractical.
|
235
|
+
case priv_key.ssh_type
|
236
|
+
when /^ssh-dss$/
|
237
|
+
Net::SSH::Buffer.from(:bignum, priv_key.p, :bignum, priv_key.q, :bignum, priv_key.g,
|
238
|
+
:bignum, priv_key.pub_key, :bignum, priv_key.priv_key).to_s
|
239
|
+
when /^ssh-dss-cert-v01@openssh\.com$/
|
240
|
+
Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.priv_key).to_s
|
241
|
+
when /^ecdsa\-sha2\-(\w*)$/
|
242
|
+
curve_name = OpenSSL::PKey::EC::CurveNameAliasInv[priv_key.group.curve_name]
|
243
|
+
Net::SSH::Buffer.from(:string, curve_name, :mstring, priv_key.public_key.to_bn.to_s(2),
|
244
|
+
:bignum, priv_key.private_key).to_s
|
245
|
+
when /^ecdsa\-sha2\-(\w*)-cert-v01@openssh\.com$/
|
246
|
+
Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.private_key).to_s
|
247
|
+
when /^ssh-ed25519$/
|
248
|
+
Net::SSH::Buffer.from(:string, priv_key.public_key.verify_key.to_bytes,
|
249
|
+
:string, priv_key.sign_key.keypair).to_s
|
250
|
+
when /^ssh-ed25519-cert-v01@openssh\.com$/
|
251
|
+
# Unlike the other certificate types, the public key is included after the certifiate.
|
252
|
+
Net::SSH::Buffer.from(:string, priv_key.to_blob,
|
253
|
+
:string, priv_key.key.public_key.verify_key.to_bytes,
|
254
|
+
:string, priv_key.key.sign_key.keypair).to_s
|
255
|
+
when /^ssh-rsa$/
|
256
|
+
# `n` and `e` are reversed compared to the ordering in `OpenSSL::PKey::RSA#to_blob`.
|
257
|
+
Net::SSH::Buffer.from(:bignum, priv_key.n, :bignum, priv_key.e, :bignum, priv_key.d,
|
258
|
+
:bignum, priv_key.iqmp, :bignum, priv_key.p, :bignum, priv_key.q).to_s
|
259
|
+
when /^ssh-rsa-cert-v01@openssh\.com$/
|
260
|
+
Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.d,
|
261
|
+
:bignum, priv_key.key.iqmp, :bignum, priv_key.key.p,
|
262
|
+
:bignum, priv_key.key.q).to_s
|
263
|
+
end
|
264
|
+
end
|
252
265
|
end
|
253
266
|
end
|
254
267
|
end
|
255
|
-
|
256
|
-
end; end; end
|
268
|
+
end
|