net-ssh 5.0.0.beta1 → 5.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.rubocop_todo.yml +98 -258
  5. data/CHANGES.txt +8 -0
  6. data/Gemfile +1 -3
  7. data/Rakefile +37 -39
  8. data/lib/net/ssh.rb +26 -25
  9. data/lib/net/ssh/authentication/agent.rb +228 -225
  10. data/lib/net/ssh/authentication/certificate.rb +166 -164
  11. data/lib/net/ssh/authentication/constants.rb +17 -14
  12. data/lib/net/ssh/authentication/ed25519.rb +107 -104
  13. data/lib/net/ssh/authentication/ed25519_loader.rb +32 -28
  14. data/lib/net/ssh/authentication/key_manager.rb +5 -3
  15. data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
  16. data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
  17. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +2 -4
  18. data/lib/net/ssh/authentication/methods/none.rb +10 -10
  19. data/lib/net/ssh/authentication/methods/password.rb +13 -13
  20. data/lib/net/ssh/authentication/methods/publickey.rb +54 -55
  21. data/lib/net/ssh/authentication/pageant.rb +468 -465
  22. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +44 -0
  23. data/lib/net/ssh/authentication/session.rb +127 -123
  24. data/lib/net/ssh/buffer.rb +305 -303
  25. data/lib/net/ssh/buffered_io.rb +163 -162
  26. data/lib/net/ssh/config.rb +230 -227
  27. data/lib/net/ssh/connection/channel.rb +659 -654
  28. data/lib/net/ssh/connection/constants.rb +30 -26
  29. data/lib/net/ssh/connection/event_loop.rb +108 -104
  30. data/lib/net/ssh/connection/keepalive.rb +54 -50
  31. data/lib/net/ssh/connection/session.rb +677 -678
  32. data/lib/net/ssh/connection/term.rb +180 -176
  33. data/lib/net/ssh/errors.rb +101 -99
  34. data/lib/net/ssh/key_factory.rb +108 -108
  35. data/lib/net/ssh/known_hosts.rb +148 -154
  36. data/lib/net/ssh/loggable.rb +56 -54
  37. data/lib/net/ssh/packet.rb +82 -78
  38. data/lib/net/ssh/prompt.rb +55 -53
  39. data/lib/net/ssh/proxy/command.rb +103 -102
  40. data/lib/net/ssh/proxy/errors.rb +12 -8
  41. data/lib/net/ssh/proxy/http.rb +92 -91
  42. data/lib/net/ssh/proxy/https.rb +42 -39
  43. data/lib/net/ssh/proxy/jump.rb +50 -47
  44. data/lib/net/ssh/proxy/socks4.rb +0 -2
  45. data/lib/net/ssh/proxy/socks5.rb +11 -11
  46. data/lib/net/ssh/ruby_compat.rb +1 -0
  47. data/lib/net/ssh/service/forward.rb +364 -362
  48. data/lib/net/ssh/test.rb +85 -83
  49. data/lib/net/ssh/test/channel.rb +146 -142
  50. data/lib/net/ssh/test/extensions.rb +148 -146
  51. data/lib/net/ssh/test/kex.rb +35 -31
  52. data/lib/net/ssh/test/local_packet.rb +48 -44
  53. data/lib/net/ssh/test/packet.rb +87 -84
  54. data/lib/net/ssh/test/remote_packet.rb +35 -31
  55. data/lib/net/ssh/test/script.rb +173 -171
  56. data/lib/net/ssh/test/socket.rb +59 -55
  57. data/lib/net/ssh/transport/algorithms.rb +413 -412
  58. data/lib/net/ssh/transport/cipher_factory.rb +108 -105
  59. data/lib/net/ssh/transport/constants.rb +35 -31
  60. data/lib/net/ssh/transport/ctr.rb +1 -1
  61. data/lib/net/ssh/transport/hmac.rb +1 -1
  62. data/lib/net/ssh/transport/hmac/abstract.rb +67 -64
  63. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +1 -1
  64. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +1 -1
  65. data/lib/net/ssh/transport/identity_cipher.rb +55 -51
  66. data/lib/net/ssh/transport/kex.rb +2 -4
  67. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +47 -40
  68. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +201 -197
  69. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -56
  70. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +94 -87
  71. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +17 -10
  72. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +17 -10
  73. data/lib/net/ssh/transport/key_expander.rb +29 -25
  74. data/lib/net/ssh/transport/openssl.rb +17 -30
  75. data/lib/net/ssh/transport/packet_stream.rb +193 -192
  76. data/lib/net/ssh/transport/server_version.rb +64 -66
  77. data/lib/net/ssh/transport/session.rb +286 -284
  78. data/lib/net/ssh/transport/state.rb +198 -196
  79. data/lib/net/ssh/verifiers/lenient.rb +29 -25
  80. data/lib/net/ssh/verifiers/null.rb +13 -9
  81. data/lib/net/ssh/verifiers/secure.rb +45 -45
  82. data/lib/net/ssh/verifiers/strict.rb +20 -16
  83. data/lib/net/ssh/version.rb +55 -53
  84. data/net-ssh.gemspec +4 -4
  85. data/support/ssh_tunnel_bug.rb +2 -2
  86. metadata +25 -24
  87. metadata.gz.sig +0 -0
@@ -1,3 +1,11 @@
1
+ === 5.0.0
2
+ * Breaking change: ed25519 now requires ed25519 gem instead of RbNaCl gem [#563]
3
+
4
+ === 5.0.0.beta2
5
+ * Support for sha256 pubkey fingerprint [Tom Maher, #585]
6
+ * Don't try to load default_keys if key_data option is used [Josh Larson, #589]
7
+ * Added fingerprint_hash defaulting to SHA256 as fingerprint format, and MD5 can be used as an option [Miklós Fazekas, #591]
8
+
1
9
  === 5.0.0.beta1
2
10
 
3
11
  * Don't leave proxy command as zombie on timeout [DimitriosLisenko, #560]
data/Gemfile CHANGED
@@ -3,9 +3,7 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in mygem.gemspec
4
4
  gemspec
5
5
 
6
- if !Gem.win_platform? && RUBY_ENGINE == "ruby"
7
- gem 'byebug', group: [:development, :test]
8
- end
6
+ gem 'byebug', group: %i[development test] if !Gem.win_platform? && RUBY_ENGINE == "ruby"
9
7
 
10
8
  if ENV["CI"]
11
9
  gem 'codecov', require: false, group: :test
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- # coding: UTF-8
1
+
2
2
  #
3
3
  # Also in your terminal environment run:
4
4
  # $ export LANG=en_US.UTF-8
@@ -12,7 +12,6 @@ require "bundler/gem_tasks"
12
12
 
13
13
  require "rdoc/task"
14
14
 
15
-
16
15
  desc "When releasing make sure NET_SSH_BUILDGEM_SIGNED is set"
17
16
  task :check_NET_SSH_BUILDGEM_SIGNED do
18
17
  raise "NET_SSH_BUILDGEM_SIGNED should be set to release" unless ENV['NET_SSH_BUILDGEM_SIGNED']
@@ -21,9 +20,8 @@ end
21
20
  Rake::Task[:release].enhance [:check_NET_SSH_BUILDGEM_SIGNED]
22
21
  Rake::Task[:release].prerequisites.unshift(:check_NET_SSH_BUILDGEM_SIGNED)
23
22
 
24
-
25
23
  task default: ["build"]
26
- CLEAN.include [ 'pkg', 'rdoc' ]
24
+ CLEAN.include ['pkg', 'rdoc']
27
25
  name = "net-ssh"
28
26
 
29
27
  require_relative "lib/net/ssh/version"
@@ -39,47 +37,47 @@ RDoc::Task.new do |rdoc|
39
37
  rdoc.rdoc_files.include("bin/*.rb")
40
38
  rdoc.rdoc_files.include("lib/**/*.rb")
41
39
  extra_files.each { |file|
42
- rdoc.rdoc_files.include(file) if File.exists?(file)
40
+ rdoc.rdoc_files.include(file) if File.exist?(file)
43
41
  }
44
42
  end
45
43
 
46
44
  namespace :cert do
47
- desc "Update public cert from private - only run if public is expired"
48
- task :update_public_when_expired do
49
- require 'openssl'
50
- require 'time'
51
- raw = File.read "net-ssh-public_cert.pem"
52
- certificate = OpenSSL::X509::Certificate.new raw
53
- raise Exception, "Not yet expired: #{certificate.not_after}" unless certificate.not_after < Time.now
54
- sh "gem cert --build netssh@solutious.com --days 365*5 --private-key /mnt/gem/net-ssh-private_key.pem"
55
- sh "mv gem-public_cert.pem net-ssh-public_cert.pem"
56
- sh "gem cert --add net-ssh-public_cert.pem"
57
- end
45
+ desc "Update public cert from private - only run if public is expired"
46
+ task :update_public_when_expired do
47
+ require 'openssl'
48
+ require 'time'
49
+ raw = File.read "net-ssh-public_cert.pem"
50
+ certificate = OpenSSL::X509::Certificate.new raw
51
+ raise Exception, "Not yet expired: #{certificate.not_after}" unless certificate.not_after < Time.now
52
+ sh "gem cert --build netssh@solutious.com --days 365*5 --private-key /mnt/gem/net-ssh-private_key.pem"
53
+ sh "mv gem-public_cert.pem net-ssh-public_cert.pem"
54
+ sh "gem cert --add net-ssh-public_cert.pem"
55
+ end
58
56
  end
59
57
 
60
58
  namespace :rdoc do
61
- desc "Update gh-pages branch"
62
- task :publish do
63
- # copy/checkout
64
- rm_rf "/tmp/net-ssh-rdoc"
65
- rm_rf "/tmp/net-ssh-gh-pages"
66
- cp_r "./rdoc", "/tmp/net-ssh-rdoc"
67
- mkdir "/tmp/net-ssh-gh-pages"
68
- Dir.chdir "/tmp/net-ssh-gh-pages" do
69
- sh "git clone --branch gh-pages --single-branch https://github.com/net-ssh/net-ssh"
70
- rm_rf "/tmp/net-ssh-gh-pages/net-ssh/*"
71
- end
72
- # update
73
- sh "cp -rf ./rdoc/* /tmp/net-ssh-gh-pages/net-ssh/"
74
- Dir.chdir "/tmp/net-ssh-gh-pages/net-ssh" do
75
- sh "git add -A ."
76
- sh "git commit -m \"Update docs\""
59
+ desc "Update gh-pages branch"
60
+ task :publish do
61
+ # copy/checkout
62
+ rm_rf "/tmp/net-ssh-rdoc"
63
+ rm_rf "/tmp/net-ssh-gh-pages"
64
+ cp_r "./rdoc", "/tmp/net-ssh-rdoc"
65
+ mkdir "/tmp/net-ssh-gh-pages"
66
+ Dir.chdir "/tmp/net-ssh-gh-pages" do
67
+ sh "git clone --branch gh-pages --single-branch https://github.com/net-ssh/net-ssh"
68
+ rm_rf "/tmp/net-ssh-gh-pages/net-ssh/*"
69
+ end
70
+ # update
71
+ sh "cp -rf ./rdoc/* /tmp/net-ssh-gh-pages/net-ssh/"
72
+ Dir.chdir "/tmp/net-ssh-gh-pages/net-ssh" do
73
+ sh "git add -A ."
74
+ sh "git commit -m \"Update docs\""
75
+ end
76
+ # publish
77
+ Dir.chdir "/tmp/net-ssh-gh-pages/net-ssh" do
78
+ sh "git push origin gh-pages"
79
+ end
77
80
  end
78
- # publish
79
- Dir.chdir "/tmp/net-ssh-gh-pages/net-ssh" do
80
- sh "git push origin gh-pages"
81
- end
82
- end
83
81
  end
84
82
 
85
83
  require 'rake/testtask'
@@ -94,7 +92,7 @@ Rake::TestTask.new do |t|
94
92
  test_files -= FileList['test/manual/test_*.rb']
95
93
  test_files -= FileList['test/test_pageant.rb']
96
94
  test_files -= FileList['test/test/**/test_*.rb']
97
- t.test_files = test_files
95
+ t.test_files = test_files
98
96
  end
99
97
 
100
98
  desc "Run tests of Net::SSH:Test"
@@ -103,5 +101,5 @@ Rake::TestTask.new do |t|
103
101
  # we need to run test/test separatedly as it hacks io + other modules
104
102
  t.libs = ["lib", "test"]
105
103
  test_files = FileList['test/test/**/test_*.rb']
106
- t.test_files = test_files
104
+ t.test_files = test_files
107
105
  end
@@ -62,17 +62,18 @@ module Net
62
62
  module SSH
63
63
  # This is the set of options that Net::SSH.start recognizes. See
64
64
  # Net::SSH.start for a description of each option.
65
- VALID_OPTIONS = [
66
- :auth_methods, :bind_address, :compression, :compression_level, :config,
67
- :encryption, :forward_agent, :hmac, :host_key, :remote_user,
68
- :keepalive, :keepalive_interval, :keepalive_maxcount, :kex, :keys, :key_data,
69
- :languages, :logger, :paranoid, :password, :port, :proxy,
70
- :rekey_blocks_limit,:rekey_limit, :rekey_packet_limit, :timeout, :verbose,
71
- :known_hosts, :global_known_hosts_file, :user_known_hosts_file, :host_key_alias,
72
- :host_name, :user, :properties, :passphrase, :keys_only, :max_pkt_size,
73
- :max_win_size, :send_env, :use_agent, :number_of_password_prompts,
74
- :append_all_supported_algorithms, :non_interactive, :password_prompt,
75
- :agent_socket_factory, :minimum_dh_bits, :verify_host_key
65
+ VALID_OPTIONS = %i[
66
+ auth_methods bind_address compression compression_level config
67
+ encryption forward_agent hmac host_key remote_user
68
+ keepalive keepalive_interval keepalive_maxcount kex keys key_data
69
+ languages logger paranoid password port proxy
70
+ rekey_blocks_limit rekey_limit rekey_packet_limit timeout verbose
71
+ known_hosts global_known_hosts_file user_known_hosts_file host_key_alias
72
+ host_name user properties passphrase keys_only max_pkt_size
73
+ max_win_size send_env use_agent number_of_password_prompts
74
+ append_all_supported_algorithms non_interactive password_prompt
75
+ agent_socket_factory minimum_dh_bits verify_host_key
76
+ fingerprint_hash
76
77
  ]
77
78
 
78
79
  # The standard means of starting a new SSH connection. When used with a
@@ -200,7 +201,7 @@ module Net
200
201
  # given to +verify+ is a hash consisting of the +:key+, the +:key_blob+,
201
202
  # the +:fingerprint+ and the +:session+. Returning true accepts the host key,
202
203
  # returning false declines it and closes the connection.
203
- #
204
+ # * :fingerprint_hash => 'MD5' or 'SHA256', defaults to 'SHA256'
204
205
  # If +user+ parameter is nil it defaults to USER from ssh_config, or
205
206
  # local username
206
207
  def self.start(host, user=nil, options={}, &block)
@@ -224,14 +225,14 @@ module Net
224
225
 
225
226
  if options[:verbose]
226
227
  options[:logger].level = case options[:verbose]
227
- when Integer then options[:verbose]
228
- when :debug then Logger::DEBUG
229
- when :info then Logger::INFO
230
- when :warn then Logger::WARN
231
- when :error then Logger::ERROR
232
- when :fatal then Logger::FATAL
233
- else raise ArgumentError, "can't convert #{options[:verbose].inspect} to any of the Logger level constants"
234
- end
228
+ when Integer then options[:verbose]
229
+ when :debug then Logger::DEBUG
230
+ when :info then Logger::INFO
231
+ when :warn then Logger::WARN
232
+ when :error then Logger::ERROR
233
+ when :fatal then Logger::FATAL
234
+ else raise ArgumentError, "can't convert #{options[:verbose].inspect} to any of the Logger level constants"
235
+ end
235
236
  end
236
237
 
237
238
  transport = Transport::Session.new(host, options)
@@ -266,10 +267,10 @@ module Net
266
267
  # See Net::SSH::Config for the full description of all supported options.
267
268
  def self.configuration_for(host, use_ssh_config)
268
269
  files = case use_ssh_config
269
- when true then Net::SSH::Config.expandable_default_files
270
- when false, nil then return {}
271
- else Array(use_ssh_config)
272
- end
270
+ when true then Net::SSH::Config.expandable_default_files
271
+ when false, nil then return {}
272
+ else Array(use_ssh_config)
273
+ end
273
274
 
274
275
  Net::SSH::Config.for(host, files)
275
276
  end
@@ -282,7 +283,7 @@ module Net
282
283
 
283
284
  options[:password_prompt] ||= Prompt.default(options)
284
285
 
285
- [:password, :passphrase].each do |key|
286
+ %i[password passphrase].each do |key|
286
287
  options.delete(key) if options.key?(key) && options[key].nil?
287
288
  end
288
289
  end
@@ -8,252 +8,255 @@ require 'rubygems'
8
8
 
9
9
  require 'net/ssh/authentication/pageant' if Gem.win_platform? && RUBY_PLATFORM != "java"
10
10
 
11
- module Net; module SSH; module Authentication
12
- # Class for representing agent-specific errors.
13
- class AgentError < Net::SSH::Exception; end
14
- # An exception for indicating that the SSH agent is not available.
15
- class AgentNotAvailable < AgentError; end
16
-
17
- # This class implements a simple client for the ssh-agent protocol. It
18
- # does not implement any specific protocol, but instead copies the
19
- # behavior of the ssh-agent functions in the OpenSSH library (3.8).
20
- #
21
- # This means that although it behaves like a SSH1 client, it also has
22
- # some SSH2 functionality (like signing data).
23
- class Agent
24
- include Loggable
25
-
26
- # A simple module for extending keys, to allow comments to be specified
27
- # for them.
28
- module Comment
29
- attr_accessor :comment
30
- end
31
-
32
- SSH2_AGENT_REQUEST_VERSION = 1
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
- SSH_AGENT_RSA_SHA2_256 = 0x02
56
- SSH_AGENT_RSA_SHA2_512 = 0x04
57
-
58
- # The underlying socket being used to communicate with the SSH agent.
59
- attr_reader :socket
60
-
61
- # Instantiates a new agent object, connects to a running SSH agent,
62
- # negotiates the agent protocol version, and returns the agent object.
63
- def self.connect(logger=nil, agent_socket_factory = nil)
64
- agent = new(logger)
65
- agent.connect!(agent_socket_factory)
66
- agent.negotiate!
67
- agent
68
- end
69
-
70
- # Creates a new Agent object, using the optional logger instance to
71
- # report status.
72
- def initialize(logger=nil)
73
- self.logger = logger
74
- 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
75
33
 
76
- # Connect to the agent process using the socket factory and socket name
77
- # given by the attribute writers. If the agent on the other end of the
78
- # socket reports that it is an SSH2-compatible agent, this will fail
79
- # (it only supports the ssh-agent distributed by OpenSSH).
80
- def connect!(agent_socket_factory = nil)
81
- debug { "connecting to ssh-agent" }
82
- @socket =
83
- if agent_socket_factory
84
- agent_socket_factory.call
85
- elsif ENV['SSH_AUTH_SOCK'] && unix_socket_class
86
- unix_socket_class.open(ENV['SSH_AUTH_SOCK'])
87
- elsif Gem.win_platform? && RUBY_ENGINE != "jruby"
88
- Pageant::Socket.open
89
- else
90
- raise AgentNotAvailable, "Agent not configured"
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)
66
+ agent = new(logger)
67
+ agent.connect!(agent_socket_factory)
68
+ agent.negotiate!
69
+ agent
91
70
  end
92
- rescue StandardError => e
93
- error { "could not connect to ssh-agent: #{e.message}" }
94
- raise AgentNotAvailable, $!.message
95
- end
96
71
 
97
- # Attempts to negotiate the SSH agent protocol version. Raises an error
98
- # if the version could not be negotiated successfully.
99
- def negotiate!
100
- # determine what type of agent we're communicating with
101
- type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)
102
-
103
- raise AgentNotAvailable, "SSH2 agents are not yet supported" if type == SSH2_AGENT_VERSION_RESPONSE
104
- if type == SSH2_AGENT_FAILURE
105
- debug { "Unexpected response type==#{type}, this will be ignored" }
106
- elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
107
- raise AgentNotAvailable, "unknown response from agent: #{type}, #{body.to_s.inspect}"
108
- end
109
- 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
110
77
 
111
- # Return an array of all identities (public keys) known to the agent.
112
- # Each key returned is augmented with a +comment+ property which is set
113
- # to the comment returned by the agent for that key.
114
- def identities
115
- type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
116
- raise AgentError, "could not get identity count" if agent_failed(type)
117
- raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
118
-
119
- identities = []
120
- body.read_long.times do
121
- key_str = body.read_string
122
- comment_str = body.read_string
123
- begin
124
- key = Buffer.new(key_str).read_key
125
- key.extend(Comment)
126
- key.comment = comment_str
127
- identities.push key
128
- rescue NotImplementedError => e
129
- error { "ignoring unimplemented key:#{e.message} #{comment_str}" }
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)
83
+ debug { "connecting to ssh-agent" }
84
+ @socket =
85
+ if agent_socket_factory
86
+ agent_socket_factory.call
87
+ elsif ENV['SSH_AUTH_SOCK'] && unix_socket_class
88
+ unix_socket_class.open(ENV['SSH_AUTH_SOCK'])
89
+ elsif Gem.win_platform? && RUBY_ENGINE != "jruby"
90
+ Pageant::Socket.open
91
+ else
92
+ raise AgentNotAvailable, "Agent not configured"
93
+ end
94
+ rescue StandardError => e
95
+ error { "could not connect to ssh-agent: #{e.message}" }
96
+ raise AgentNotAvailable, $!.message
130
97
  end
131
- end
132
98
 
133
- return identities
134
- end
99
+ # Attempts to negotiate the SSH agent protocol version. Raises an error
100
+ # if the version could not be negotiated successfully.
101
+ def negotiate!
102
+ # determine what type of agent we're communicating with
103
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)
104
+
105
+ raise AgentNotAvailable, "SSH2 agents are not yet supported" if type == SSH2_AGENT_VERSION_RESPONSE
106
+ if type == SSH2_AGENT_FAILURE
107
+ debug { "Unexpected response type==#{type}, this will be ignored" }
108
+ elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
109
+ raise AgentNotAvailable, "unknown response from agent: #{type}, #{body.to_s.inspect}"
110
+ end
111
+ end
135
112
 
136
- # Closes this socket. This agent reference is no longer able to
137
- # query the agent.
138
- def close
139
- @socket.close
140
- end
113
+ # Return an array of all identities (public keys) known to the agent.
114
+ # Each key returned is augmented with a +comment+ property which is set
115
+ # to the comment returned by the agent for that key.
116
+ def identities
117
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
118
+ raise AgentError, "could not get identity count" if agent_failed(type)
119
+ raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
120
+
121
+ identities = []
122
+ body.read_long.times do
123
+ key_str = body.read_string
124
+ comment_str = body.read_string
125
+ begin
126
+ key = Buffer.new(key_str).read_key
127
+ key.extend(Comment)
128
+ key.comment = comment_str
129
+ identities.push key
130
+ rescue NotImplementedError => e
131
+ error { "ignoring unimplemented key:#{e.message} #{comment_str}" }
132
+ end
133
+ end
134
+
135
+ return identities
136
+ end
141
137
 
142
- # Using the agent and the given public key, sign the given data. The
143
- # signature is returned in SSH2 format.
144
- def sign(key, data, flags = 0)
145
- type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, flags)
138
+ # Closes this socket. This agent reference is no longer able to
139
+ # query the agent.
140
+ def close
141
+ @socket.close
142
+ end
146
143
 
147
- raise AgentError, "agent could not sign data with requested identity" if agent_failed(type)
148
- raise AgentError, "bad authentication response #{type}" if type != SSH2_AGENT_SIGN_RESPONSE
144
+ # Using the agent and the given public key, sign the given data. The
145
+ # signature is returned in SSH2 format.
146
+ def sign(key, data, flags = 0)
147
+ type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, flags)
149
148
 
150
- return reply.read_string
151
- end
149
+ raise AgentError, "agent could not sign data with requested identity" if agent_failed(type)
150
+ raise AgentError, "bad authentication response #{type}" if type != SSH2_AGENT_SIGN_RESPONSE
152
151
 
153
- # Adds the private key with comment to the agent.
154
- # If lifetime is given, the key will automatically be removed after lifetime
155
- # seconds.
156
- # If confirm is true, confirmation will be required for each agent signing
157
- # operation.
158
- def add_identity(priv_key, comment, lifetime: nil, confirm: false)
159
- constraints = Buffer.new
160
- if lifetime
161
- constraints.write_byte(SSH_AGENT_CONSTRAIN_LIFETIME)
162
- constraints.write_long(lifetime)
163
- end
164
- constraints.write_byte(SSH_AGENT_CONSTRAIN_CONFIRM) if confirm
152
+ return reply.read_string
153
+ end
165
154
 
166
- req_type = constraints.empty? ? SSH2_AGENT_ADD_IDENTITY : SSH2_AGENT_ADD_ID_CONSTRAINED
167
- type, = send_and_wait(req_type, :string, priv_key.ssh_type, :raw, blob_for_add(priv_key),
168
- :string, comment, :raw, constraints)
169
- raise AgentError, "could not add identity to agent" if type != SSH_AGENT_SUCCESS
170
- end
155
+ # Adds the private key with comment to the agent.
156
+ # If lifetime is given, the key will automatically be removed after lifetime
157
+ # seconds.
158
+ # If confirm is true, confirmation will be required for each agent signing
159
+ # operation.
160
+ def add_identity(priv_key, comment, lifetime: nil, confirm: false)
161
+ constraints = Buffer.new
162
+ if lifetime
163
+ constraints.write_byte(SSH_AGENT_CONSTRAIN_LIFETIME)
164
+ constraints.write_long(lifetime)
165
+ end
166
+ constraints.write_byte(SSH_AGENT_CONSTRAIN_CONFIRM) if confirm
167
+
168
+ req_type = constraints.empty? ? SSH2_AGENT_ADD_IDENTITY : SSH2_AGENT_ADD_ID_CONSTRAINED
169
+ type, = send_and_wait(req_type, :string, priv_key.ssh_type, :raw, blob_for_add(priv_key),
170
+ :string, comment, :raw, constraints)
171
+ raise AgentError, "could not add identity to agent" if type != SSH_AGENT_SUCCESS
172
+ end
171
173
 
172
- # Removes key from the agent.
173
- def remove_identity(key)
174
- type, = send_and_wait(SSH2_AGENT_REMOVE_IDENTITY, :string, key.to_blob)
175
- raise AgentError, "could not remove identity from agent" if type != SSH_AGENT_SUCCESS
176
- end
174
+ # Removes key from the agent.
175
+ def remove_identity(key)
176
+ type, = send_and_wait(SSH2_AGENT_REMOVE_IDENTITY, :string, key.to_blob)
177
+ raise AgentError, "could not remove identity from agent" if type != SSH_AGENT_SUCCESS
178
+ end
177
179
 
178
- # Removes all identities from the agent.
179
- def remove_all_identities
180
- type, = send_and_wait(SSH2_AGENT_REMOVE_ALL_IDENTITIES)
181
- raise AgentError, "could not remove all identity from agent" if type != SSH_AGENT_SUCCESS
182
- end
180
+ # Removes all identities from the agent.
181
+ def remove_all_identities
182
+ type, = send_and_wait(SSH2_AGENT_REMOVE_ALL_IDENTITIES)
183
+ raise AgentError, "could not remove all identity from agent" if type != SSH_AGENT_SUCCESS
184
+ end
183
185
 
184
- private
186
+ private
185
187
 
186
- def unix_socket_class
187
- defined?(UNIXSocket) && UNIXSocket
188
- end
188
+ def unix_socket_class
189
+ defined?(UNIXSocket) && UNIXSocket
190
+ end
189
191
 
190
- # Send a new packet of the given type, with the associated data.
191
- def send_packet(type, *args)
192
- buffer = Buffer.from(*args)
193
- data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
194
- debug { "sending agent request #{type} len #{buffer.length}" }
195
- @socket.send data, 0
196
- end
192
+ # Send a new packet of the given type, with the associated data.
193
+ def send_packet(type, *args)
194
+ buffer = Buffer.from(*args)
195
+ data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
196
+ debug { "sending agent request #{type} len #{buffer.length}" }
197
+ @socket.send data, 0
198
+ end
197
199
 
198
- # Read the next packet from the agent. This will return a two-part
199
- # tuple consisting of the packet type, and the packet's body (which
200
- # is returned as a Net::SSH::Buffer).
201
- def read_packet
202
- buffer = Net::SSH::Buffer.new(@socket.read(4))
203
- buffer.append(@socket.read(buffer.read_long))
204
- type = buffer.read_byte
205
- debug { "received agent packet #{type} len #{buffer.length-4}" }
206
- return type, buffer
207
- end
200
+ # Read the next packet from the agent. This will return a two-part
201
+ # tuple consisting of the packet type, and the packet's body (which
202
+ # is returned as a Net::SSH::Buffer).
203
+ def read_packet
204
+ buffer = Net::SSH::Buffer.new(@socket.read(4))
205
+ buffer.append(@socket.read(buffer.read_long))
206
+ type = buffer.read_byte
207
+ debug { "received agent packet #{type} len #{buffer.length - 4}" }
208
+ return type, buffer
209
+ end
208
210
 
209
- # Send the given packet and return the subsequent reply from the agent.
210
- # (See #send_packet and #read_packet).
211
- def send_and_wait(type, *args)
212
- send_packet(type, *args)
213
- read_packet
214
- end
211
+ # Send the given packet and return the subsequent reply from the agent.
212
+ # (See #send_packet and #read_packet).
213
+ def send_and_wait(type, *args)
214
+ send_packet(type, *args)
215
+ read_packet
216
+ end
215
217
 
216
- # Returns +true+ if the parameter indicates a "failure" response from
217
- # the agent, and +false+ otherwise.
218
- def agent_failed(type)
219
- type == SSH_AGENT_FAILURE ||
220
- type == SSH2_AGENT_FAILURE ||
221
- type == SSH_COM_AGENT2_FAILURE
222
- end
218
+ # Returns +true+ if the parameter indicates a "failure" response from
219
+ # the agent, and +false+ otherwise.
220
+ def agent_failed(type)
221
+ type == SSH_AGENT_FAILURE ||
222
+ type == SSH2_AGENT_FAILURE ||
223
+ type == SSH_COM_AGENT2_FAILURE
224
+ end
223
225
 
224
- def blob_for_add(priv_key)
225
- # Ideally we'd have something like `to_private_blob` on the various key types, but the
226
- # nuances with encoding (e.g. `n` and `e` are reversed for RSA keys) make this impractical.
227
- case priv_key.ssh_type
228
- when /^ssh-dss$/
229
- Net::SSH::Buffer.from(:bignum, priv_key.p, :bignum, priv_key.q, :bignum, priv_key.g,
230
- :bignum, priv_key.pub_key, :bignum, priv_key.priv_key).to_s
231
- when /^ssh-dss-cert-v01@openssh\.com$/
232
- Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.priv_key).to_s
233
- when /^ecdsa\-sha2\-(\w*)$/
234
- curve_name = OpenSSL::PKey::EC::CurveNameAliasInv[priv_key.group.curve_name]
235
- Net::SSH::Buffer.from(:string, curve_name, :mstring, priv_key.public_key.to_bn.to_s(2),
236
- :bignum, priv_key.private_key).to_s
237
- when /^ecdsa\-sha2\-(\w*)-cert-v01@openssh\.com$/
238
- Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.private_key).to_s
239
- when /^ssh-ed25519$/
240
- Net::SSH::Buffer.from(:string, priv_key.public_key.verify_key.to_bytes,
241
- :string, priv_key.sign_key.keypair).to_s
242
- when /^ssh-ed25519-cert-v01@openssh\.com$/
243
- # Unlike the other certificate types, the public key is included after the certifiate.
244
- Net::SSH::Buffer.from(:string, priv_key.to_blob,
245
- :string, priv_key.key.public_key.verify_key.to_bytes,
246
- :string, priv_key.key.sign_key.keypair).to_s
247
- when /^ssh-rsa$/
248
- # `n` and `e` are reversed compared to the ordering in `OpenSSL::PKey::RSA#to_blob`.
249
- Net::SSH::Buffer.from(:bignum, priv_key.n, :bignum, priv_key.e, :bignum, priv_key.d,
250
- :bignum, priv_key.iqmp, :bignum, priv_key.p, :bignum, priv_key.q).to_s
251
- when /^ssh-rsa-cert-v01@openssh\.com$/
252
- Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.d,
253
- :bignum, priv_key.key.iqmp, :bignum, priv_key.key.p,
254
- :bignum, priv_key.key.q).to_s
226
+ def blob_for_add(priv_key)
227
+ # Ideally we'd have something like `to_private_blob` on the various key types, but the
228
+ # nuances with encoding (e.g. `n` and `e` are reversed for RSA keys) make this impractical.
229
+ case priv_key.ssh_type
230
+ when /^ssh-dss$/
231
+ Net::SSH::Buffer.from(:bignum, priv_key.p, :bignum, priv_key.q, :bignum, priv_key.g,
232
+ :bignum, priv_key.pub_key, :bignum, priv_key.priv_key).to_s
233
+ when /^ssh-dss-cert-v01@openssh\.com$/
234
+ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.priv_key).to_s
235
+ when /^ecdsa\-sha2\-(\w*)$/
236
+ curve_name = OpenSSL::PKey::EC::CurveNameAliasInv[priv_key.group.curve_name]
237
+ Net::SSH::Buffer.from(:string, curve_name, :mstring, priv_key.public_key.to_bn.to_s(2),
238
+ :bignum, priv_key.private_key).to_s
239
+ when /^ecdsa\-sha2\-(\w*)-cert-v01@openssh\.com$/
240
+ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.private_key).to_s
241
+ when /^ssh-ed25519$/
242
+ Net::SSH::Buffer.from(:string, priv_key.public_key.verify_key.to_bytes,
243
+ :string, priv_key.sign_key.keypair).to_s
244
+ when /^ssh-ed25519-cert-v01@openssh\.com$/
245
+ # Unlike the other certificate types, the public key is included after the certifiate.
246
+ Net::SSH::Buffer.from(:string, priv_key.to_blob,
247
+ :string, priv_key.key.public_key.verify_key.to_bytes,
248
+ :string, priv_key.key.sign_key.keypair).to_s
249
+ when /^ssh-rsa$/
250
+ # `n` and `e` are reversed compared to the ordering in `OpenSSL::PKey::RSA#to_blob`.
251
+ Net::SSH::Buffer.from(:bignum, priv_key.n, :bignum, priv_key.e, :bignum, priv_key.d,
252
+ :bignum, priv_key.iqmp, :bignum, priv_key.p, :bignum, priv_key.q).to_s
253
+ when /^ssh-rsa-cert-v01@openssh\.com$/
254
+ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.d,
255
+ :bignum, priv_key.key.iqmp, :bignum, priv_key.key.p,
256
+ :bignum, priv_key.key.q).to_s
257
+ end
258
+ end
255
259
  end
256
260
  end
257
261
  end
258
-
259
- end; end; end
262
+ end