net-ssh 5.0.2 → 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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +8 -2
  6. data/.rubocop_todo.yml +392 -379
  7. data/.travis.yml +22 -22
  8. data/CHANGES.txt +63 -0
  9. data/Manifest +0 -1
  10. data/README.md +287 -0
  11. data/Rakefile +1 -2
  12. data/appveyor.yml +4 -2
  13. data/lib/net/ssh.rb +13 -4
  14. data/lib/net/ssh/authentication/agent.rb +9 -3
  15. data/lib/net/ssh/authentication/certificate.rb +10 -1
  16. data/lib/net/ssh/authentication/ed25519.rb +75 -46
  17. data/lib/net/ssh/authentication/ed25519_loader.rb +1 -1
  18. data/lib/net/ssh/authentication/key_manager.rb +35 -6
  19. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +3 -1
  20. data/lib/net/ssh/authentication/methods/publickey.rb +2 -0
  21. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +0 -1
  22. data/lib/net/ssh/authentication/session.rb +9 -6
  23. data/lib/net/ssh/buffer.rb +41 -10
  24. data/lib/net/ssh/buffered_io.rb +0 -1
  25. data/lib/net/ssh/config.rb +68 -35
  26. data/lib/net/ssh/connection/channel.rb +17 -5
  27. data/lib/net/ssh/connection/event_loop.rb +0 -1
  28. data/lib/net/ssh/connection/session.rb +7 -4
  29. data/lib/net/ssh/key_factory.rb +104 -17
  30. data/lib/net/ssh/known_hosts.rb +41 -26
  31. data/lib/net/ssh/loggable.rb +2 -2
  32. data/lib/net/ssh/proxy/command.rb +0 -1
  33. data/lib/net/ssh/proxy/socks5.rb +0 -1
  34. data/lib/net/ssh/service/forward.rb +2 -1
  35. data/lib/net/ssh/test.rb +8 -7
  36. data/lib/net/ssh/test/extensions.rb +2 -0
  37. data/lib/net/ssh/transport/algorithms.rb +161 -105
  38. data/lib/net/ssh/transport/cipher_factory.rb +11 -27
  39. data/lib/net/ssh/transport/constants.rb +10 -6
  40. data/lib/net/ssh/transport/ctr.rb +1 -7
  41. data/lib/net/ssh/transport/hmac.rb +15 -13
  42. data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
  43. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  44. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  45. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  46. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  47. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  48. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  49. data/lib/net/ssh/transport/kex.rb +14 -11
  50. data/lib/net/ssh/transport/kex/abstract.rb +123 -0
  51. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  52. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
  53. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  54. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +1 -15
  55. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +9 -118
  56. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +0 -6
  57. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  58. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +18 -79
  59. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +5 -4
  60. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +5 -4
  61. data/lib/net/ssh/transport/openssl.rb +106 -93
  62. data/lib/net/ssh/transport/packet_stream.rb +52 -20
  63. data/lib/net/ssh/transport/session.rb +26 -4
  64. data/lib/net/ssh/transport/state.rb +1 -1
  65. data/lib/net/ssh/verifiers/accept_new.rb +7 -0
  66. data/lib/net/ssh/verifiers/always.rb +4 -0
  67. data/lib/net/ssh/verifiers/never.rb +4 -0
  68. data/lib/net/ssh/version.rb +3 -3
  69. data/net-ssh-public_cert.pem +18 -19
  70. data/net-ssh.gemspec +9 -7
  71. metadata +56 -40
  72. metadata.gz.sig +0 -0
  73. data/Gemfile.noed25519.lock +0 -41
  74. data/README.rdoc +0 -169
  75. data/lib/net/ssh/ruby_compat.rb +0 -13
  76. data/support/arcfour_check.rb +0 -20
@@ -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'
@@ -66,14 +67,14 @@ module Net
66
67
  auth_methods bind_address compression compression_level config
67
68
  encryption forward_agent hmac host_key remote_user
68
69
  keepalive keepalive_interval keepalive_maxcount kex keys key_data
69
- languages logger paranoid password port proxy
70
+ keycerts languages logger paranoid password port proxy
70
71
  rekey_blocks_limit rekey_limit rekey_packet_limit timeout verbose
71
72
  known_hosts global_known_hosts_file user_known_hosts_file host_key_alias
72
73
  host_name user properties passphrase keys_only max_pkt_size
73
- max_win_size send_env use_agent number_of_password_prompts
74
+ max_win_size send_env set_env use_agent number_of_password_prompts
74
75
  append_all_supported_algorithms non_interactive password_prompt
75
76
  agent_socket_factory minimum_dh_bits verify_host_key
76
- fingerprint_hash
77
+ fingerprint_hash check_host_ip
77
78
  ]
78
79
 
79
80
  # The standard means of starting a new SSH connection. When used with a
@@ -108,6 +109,8 @@ module Net
108
109
  # * :bind_address => the IP address on the connecting machine to use in
109
110
  # establishing connection. (:bind_address is discarded if :proxy
110
111
  # is set.)
112
+ # * :check_host_ip => Also ckeck IP address when connecting to remote host.
113
+ # Defaults to +true+.
111
114
  # * :compression => the compression algorithm to use, or +true+ to use
112
115
  # whatever is supported.
113
116
  # * :compression_level => the compression level to use when sending data
@@ -142,6 +145,8 @@ module Net
142
145
  # * :kex => the key exchange algorithm (or algorithms) to use
143
146
  # * :keys => an array of file names of private keys to use for publickey
144
147
  # and hostbased authentication
148
+ # * :keycerts => an array of file names of key certificates to use
149
+ # with publickey authentication
145
150
  # * :key_data => an array of strings, with each element of the array being
146
151
  # a raw private key in PEM format.
147
152
  # * :keys_only => set to +true+ to use only private keys from +keys+ and
@@ -171,6 +176,8 @@ module Net
171
176
  # * :rekey_packet_limit => the max number of packets to process before rekeying
172
177
  # * :send_env => an array of local environment variable names to export to the
173
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+.
174
181
  # * :timeout => how long to wait for the initial connection to be made
175
182
  # * :user => the user name to log in as; this overrides the +user+
176
183
  # parameter, and is primarily only useful when provided via an SSH
@@ -221,6 +228,8 @@ module Net
221
228
  options = configuration_for(host, options.fetch(:config, true)).merge(options)
222
229
  host = options.fetch(:host_name, host)
223
230
 
231
+ options[:check_host_ip] = true unless options.key?(:check_host_ip)
232
+
224
233
  if options[:non_interactive]
225
234
  options[:number_of_password_prompts] = 0
226
235
  end
@@ -242,7 +251,7 @@ module Net
242
251
  transport = Transport::Session.new(host, options)
243
252
  auth = Authentication::Session.new(transport, options)
244
253
 
245
- user = options.fetch(:user, user) || Etc.getlogin
254
+ user = options.fetch(:user, user) || Etc.getpwuid.name
246
255
  if auth.authenticate("ssh-connection", user, options[:password])
247
256
  connection = Connection::Session.new(transport, options)
248
257
  if block_given?
@@ -62,9 +62,9 @@ module Net
62
62
 
63
63
  # Instantiates a new agent object, connects to a running SSH agent,
64
64
  # negotiates the agent protocol version, and returns the agent object.
65
- def self.connect(logger=nil, agent_socket_factory = nil)
65
+ def self.connect(logger=nil, agent_socket_factory = nil, identity_agent = nil)
66
66
  agent = new(logger)
67
- agent.connect!(agent_socket_factory)
67
+ agent.connect!(agent_socket_factory, identity_agent)
68
68
  agent.negotiate!
69
69
  agent
70
70
  end
@@ -79,11 +79,13 @@ module Net
79
79
  # given by the attribute writers. If the agent on the other end of the
80
80
  # socket reports that it is an SSH2-compatible agent, this will fail
81
81
  # (it only supports the ssh-agent distributed by OpenSSH).
82
- def connect!(agent_socket_factory = nil)
82
+ def connect!(agent_socket_factory = nil, identity_agent = nil)
83
83
  debug { "connecting to ssh-agent" }
84
84
  @socket =
85
85
  if agent_socket_factory
86
86
  agent_socket_factory.call
87
+ elsif identity_agent
88
+ unix_socket_class.open(identity_agent)
87
89
  elsif ENV['SSH_AUTH_SOCK'] && unix_socket_class
88
90
  unix_socket_class.open(ENV['SSH_AUTH_SOCK'])
89
91
  elsif Gem.win_platform? && RUBY_ENGINE != "jruby"
@@ -124,6 +126,10 @@ module Net
124
126
  comment_str = body.read_string
125
127
  begin
126
128
  key = Buffer.new(key_str).read_key
129
+ if key.nil?
130
+ error { "ignoring invalid key: #{comment_str}" }
131
+ next
132
+ end
127
133
  key.extend(Comment)
128
134
  key.comment = comment_str
129
135
  identities.push key
@@ -31,7 +31,16 @@ module Net
31
31
  cert.key_id = buffer.read_string
32
32
  cert.valid_principals = buffer.read_buffer.read_all(&:read_string)
33
33
  cert.valid_after = Time.at(buffer.read_int64)
34
- cert.valid_before = Time.at(buffer.read_int64)
34
+
35
+ cert.valid_before = if RUBY_PLATFORM == "java"
36
+ # 0x20c49ba5e353f7 = 0x7fffffffffffffff/1000, the largest value possible for JRuby
37
+ # JRuby Time.at multiplies the arg by 1000, and then stores it in a signed long.
38
+ # 0x20c49ba5e353f7 = 292278994-08-17 01:12:55 -0600
39
+ Time.at([0x20c49ba5e353f7, buffer.read_int64].min)
40
+ else
41
+ Time.at(buffer.read_int64)
42
+ end
43
+
35
44
  cert.critical_options = read_options(buffer)
36
45
  cert.extensions = read_options(buffer)
37
46
  cert.reserved = buffer.read_string
@@ -22,51 +22,26 @@ module Net
22
22
  end
23
23
  end
24
24
 
25
- class PubKey
26
- include Net::SSH::Authentication::PubKeyFingerprint
27
-
28
- attr_reader :verify_key
29
-
30
- def initialize(data)
31
- @verify_key = ::Ed25519::VerifyKey.new(data)
32
- end
33
-
34
- def self.read_keyblob(buffer)
35
- PubKey.new(buffer.read_string)
36
- end
37
-
38
- def to_blob
39
- Net::SSH::Buffer.from(:mstring,"ssh-ed25519",:string,@verify_key.to_bytes).to_s
40
- end
41
-
42
- def ssh_type
43
- "ssh-ed25519"
44
- end
45
-
46
- def ssh_signature_type
47
- ssh_type
48
- end
49
-
50
- def ssh_do_verify(sig,data)
51
- @verify_key.verify(sig,data)
52
- end
53
-
54
- def to_pem
55
- # TODO this is not pem
56
- ssh_type + Base64.encode64(@verify_key.to_bytes)
57
- end
58
- end
59
-
60
- class PrivKey
25
+ class OpenSSHPrivateKeyLoader
61
26
  CipherFactory = Net::SSH::Transport::CipherFactory
62
27
 
63
28
  MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
64
- MEND = "-----END OPENSSH PRIVATE KEY-----\n"
29
+ MEND = "-----END OPENSSH PRIVATE KEY-----"
65
30
  MAGIC = "openssh-key-v1"
66
31
 
67
- attr_reader :sign_key
32
+ class DecryptError < ArgumentError
33
+ def initialize(message, encrypted_key: false)
34
+ super(message)
35
+ @encrypted_key = encrypted_key
36
+ end
68
37
 
69
- def initialize(datafull,password)
38
+ def encrypted_key?
39
+ return @encrypted_key
40
+ end
41
+ end
42
+
43
+ def self.read(datafull, password)
44
+ datafull = datafull.strip
70
45
  raise ArgumentError.new("Expected #{MBEGIN} at start of private key") unless datafull.start_with?(MBEGIN)
71
46
  raise ArgumentError.new("Expected #{MEND} at end of private key") unless datafull.end_with?(MEND)
72
47
  datab64 = datafull[MBEGIN.size...-MEND.size]
@@ -111,12 +86,66 @@ module Net
111
86
  check1 = decoded.read_long
112
87
  check2 = decoded.read_long
113
88
 
114
- raise ArgumentError, "Decrypt failed on private key" if (check1 != check2)
89
+ raise DecryptError.new("Decrypt failed on private key", encrypted_key: kdfname == 'bcrypt') if (check1 != check2)
90
+
91
+ type_name = decoded.read_string
92
+ case type_name
93
+ when "ssh-ed25519"
94
+ PrivKey.new(decoded)
95
+ else
96
+ decoded.read_private_keyblob(type_name)
97
+ end
98
+ end
99
+ end
100
+
101
+ class PubKey
102
+ include Net::SSH::Authentication::PubKeyFingerprint
103
+
104
+ attr_reader :verify_key
105
+
106
+ def initialize(data)
107
+ @verify_key = ::Ed25519::VerifyKey.new(data)
108
+ end
109
+
110
+ def self.read_keyblob(buffer)
111
+ PubKey.new(buffer.read_string)
112
+ end
113
+
114
+ def to_blob
115
+ Net::SSH::Buffer.from(:mstring,"ssh-ed25519",:string,@verify_key.to_bytes).to_s
116
+ end
117
+
118
+ def ssh_type
119
+ "ssh-ed25519"
120
+ end
121
+
122
+ def ssh_signature_type
123
+ ssh_type
124
+ end
125
+
126
+ def ssh_do_verify(sig,data)
127
+ @verify_key.verify(sig,data)
128
+ end
129
+
130
+ def to_pem
131
+ # TODO this is not pem
132
+ ssh_type + Base64.encode64(@verify_key.to_bytes)
133
+ end
134
+ end
135
+
136
+ class PrivKey
137
+ CipherFactory = Net::SSH::Transport::CipherFactory
138
+
139
+ MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
140
+ MEND = "-----END OPENSSH PRIVATE KEY-----\n"
141
+ MAGIC = "openssh-key-v1"
142
+
143
+ attr_reader :sign_key
115
144
 
116
- _type_name = decoded.read_string
117
- pk = decoded.read_string
118
- sk = decoded.read_string
119
- _comment = decoded.read_string
145
+ def initialize(buffer)
146
+ pk = buffer.read_string
147
+ sk = buffer.read_string
148
+ _comment = buffer.read_string
120
149
 
121
150
  @pk = pk
122
151
  @sign_key = SigningKeyFromFile.new(pk,sk)
@@ -142,8 +171,8 @@ module Net
142
171
  @sign_key.sign(data)
143
172
  end
144
173
 
145
- def self.read(data,password)
146
- self.new(data,password)
174
+ def self.read(data, password)
175
+ OpenSSHPrivateKeyLoader.read(data, password)
147
176
  end
148
177
  end
149
178
  end
@@ -3,7 +3,7 @@ module Net
3
3
  module Authentication
4
4
 
5
5
  # Loads ED25519 support which requires optinal dependecies like
6
- # rbnacl, bcrypt_pbkdf
6
+ # ed25519, bcrypt_pbkdf
7
7
  module ED25519Loader
8
8
 
9
9
  begin
@@ -30,6 +30,9 @@ module Net
30
30
  # The list of user key data that will be examined
31
31
  attr_reader :key_data
32
32
 
33
+ # The list of user key certificate files that will be examined
34
+ attr_reader :keycert_files
35
+
33
36
  # The map of loaded identities
34
37
  attr_reader :known_identities
35
38
 
@@ -43,6 +46,7 @@ module Net
43
46
  self.logger = logger
44
47
  @key_files = []
45
48
  @key_data = []
49
+ @keycert_files = []
46
50
  @use_agent = options[:use_agent] != false
47
51
  @known_identities = {}
48
52
  @agent = nil
@@ -66,6 +70,12 @@ module Net
66
70
  self
67
71
  end
68
72
 
73
+ # Add the given keycert_file to the list of keycert files that will be used.
74
+ def add_keycert(keycert_file)
75
+ keycert_files.push(File.expand_path(keycert_file)).uniq!
76
+ self
77
+ end
78
+
69
79
  # Add the given key_file to the list of keys that will be used.
70
80
  def add_key_data(key_data_)
71
81
  key_data.push(key_data_).uniq!
@@ -108,7 +118,7 @@ module Net
108
118
  user_identities.delete(corresponding_user_identity) if corresponding_user_identity
109
119
 
110
120
  if !options[:keys_only] || corresponding_user_identity
111
- known_identities[key] = { from: :agent }
121
+ known_identities[key] = { from: :agent, identity: key }
112
122
  yield key
113
123
  end
114
124
  end
@@ -122,6 +132,21 @@ module Net
122
132
  yield key
123
133
  end
124
134
 
135
+ known_identity_blobs = known_identities.keys.map(&:to_blob)
136
+ keycert_files.each do |keycert_file|
137
+ keycert = KeyFactory.load_public_key(keycert_file)
138
+ next if known_identity_blobs.include?(keycert.to_blob)
139
+
140
+ (_, corresponding_identity) = known_identities.detect { |public_key, _|
141
+ public_key.to_pem == keycert.to_pem
142
+ }
143
+
144
+ if corresponding_identity
145
+ known_identities[keycert] = corresponding_identity
146
+ yield keycert
147
+ end
148
+ end
149
+
125
150
  self
126
151
  end
127
152
 
@@ -139,7 +164,7 @@ module Net
139
164
 
140
165
  if info[:key].nil? && info[:from] == :file
141
166
  begin
142
- info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive])
167
+ info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive], options[:password_prompt])
143
168
  rescue OpenSSL::OpenSSLError, Exception => e
144
169
  raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})"
145
170
  end
@@ -152,7 +177,7 @@ module Net
152
177
 
153
178
  if info[:from] == :agent
154
179
  raise KeyManagerError, "the agent is no longer available" unless agent
155
- return agent.sign(identity, data.to_s)
180
+ return agent.sign(info[:identity], data.to_s)
156
181
  end
157
182
 
158
183
  raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})"
@@ -176,7 +201,7 @@ module Net
176
201
  # or if the agent is otherwise not available.
177
202
  def agent
178
203
  return unless use_agent?
179
- @agent ||= Agent.connect(logger, options[:agent_socket_factory])
204
+ @agent ||= Agent.connect(logger, options[:agent_socket_factory], options[:identity_agent])
180
205
  rescue AgentNotAvailable
181
206
  @use_agent = false
182
207
  nil
@@ -229,11 +254,15 @@ module Net
229
254
  key = KeyFactory.load_public_key(identity[:pubkey_file])
230
255
  { public_key: key, from: :file, file: identity[:privkey_file] }
231
256
  when :privkey_file
232
- private_key = KeyFactory.load_private_key(identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt])
257
+ private_key = KeyFactory.load_private_key(
258
+ identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt]
259
+ )
233
260
  key = private_key.send(:public_key)
234
261
  { public_key: key, from: :file, file: identity[:privkey_file], key: private_key }
235
262
  when :data
236
- private_key = KeyFactory.load_data_private_key(identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt])
263
+ private_key = KeyFactory.load_data_private_key(
264
+ identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt]
265
+ )
237
266
  key = private_key.send(:public_key)
238
267
  { public_key: key, from: :key_data, data: identity[:data], key: private_key }
239
268
  else
@@ -40,7 +40,9 @@ module Net
40
40
  instruction = message.read_string
41
41
  debug { "keyboard-interactive info request" }
42
42
 
43
- prompter = prompt.start(type: 'keyboard-interactive', name: name, instruction: instruction) if password.nil? && interactive? && prompter.nil?
43
+ if password.nil? && interactive? && prompter.nil?
44
+ prompter = prompt.start(type: 'keyboard-interactive', name: name, instruction: instruction)
45
+ end
44
46
 
45
47
  _ = message.read_string # lang_tag
46
48
  responses = []
@@ -82,6 +82,8 @@ module Net
82
82
 
83
83
  when USERAUTH_FAILURE
84
84
  return false
85
+ when USERAUTH_SUCCESS
86
+ return true
85
87
 
86
88
  else
87
89
  raise Net::SSH::Exception, "unexpected reply to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
@@ -1,4 +1,3 @@
1
-
2
1
  require 'openssl'
3
2
 
4
3
  module Net
@@ -63,6 +63,7 @@ module Net
63
63
 
64
64
  key_manager = KeyManager.new(logger, options)
65
65
  keys.each { |key| key_manager.add(key) } unless keys.empty?
66
+ keycerts.each { |keycert| key_manager.add_keycert(keycert) } unless keycerts.empty?
66
67
  key_data.each { |key2| key_manager.add_key_data(key2) } unless key_data.empty?
67
68
  default_keys.each { |key| key_manager.add(key) } unless options.key?(:keys) || options.key?(:key_data)
68
69
 
@@ -136,12 +137,8 @@ module Net
136
137
  # Returns an array of paths to the key files usually defined
137
138
  # by system default.
138
139
  def default_keys
139
- if defined?(OpenSSL::PKey::EC)
140
- %w[~/.ssh/id_ed25519 ~/.ssh/id_rsa ~/.ssh/id_dsa ~/.ssh/id_ecdsa
141
- ~/.ssh2/id_ed25519 ~/.ssh2/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_ecdsa]
142
- else
143
- %w[~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa]
144
- end
140
+ %w[~/.ssh/id_ed25519 ~/.ssh/id_rsa ~/.ssh/id_dsa ~/.ssh/id_ecdsa
141
+ ~/.ssh2/id_ed25519 ~/.ssh2/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_ecdsa]
145
142
  end
146
143
 
147
144
  # Returns an array of paths to the key files that should be used when
@@ -150,6 +147,12 @@ module Net
150
147
  Array(options[:keys])
151
148
  end
152
149
 
150
+ # Returns an array of paths to the keycert files that should be used when
151
+ # attempting any key-based authentication mechanism.
152
+ def keycerts
153
+ Array(options[:keycerts])
154
+ end
155
+
153
156
  # Returns an array of the key data that should be used when
154
157
  # attempting any key-based authentication mechanism.
155
158
  def key_data