net-ssh 5.0.2 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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