net-ssh 5.0.0.beta1 → 5.0.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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