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
@@ -1,4 +1,3 @@
1
- require 'net/ssh/ruby_compat'
2
1
  require 'net/ssh/transport/openssl'
3
2
 
4
3
  require 'net/ssh/authentication/certificate'
@@ -249,6 +248,46 @@ module Net
249
248
  return (type ? read_keyblob(type) : nil)
250
249
  end
251
250
 
251
+ def read_private_keyblob(type)
252
+ case type
253
+ when /^ssh-rsa$/
254
+ key = OpenSSL::PKey::RSA.new
255
+ n = read_bignum
256
+ e = read_bignum
257
+ d = read_bignum
258
+ iqmp = read_bignum
259
+ p = read_bignum
260
+ q = read_bignum
261
+ _unkown1 = read_bignum
262
+ _unkown2 = read_bignum
263
+ dmp1 = d % (p - 1)
264
+ dmq1 = d % (q - 1)
265
+ if key.respond_to?(:set_key)
266
+ key.set_key(n, e, d)
267
+ else
268
+ key.e = e
269
+ key.n = n
270
+ key.d = d
271
+ end
272
+ if key.respond_to?(:set_factors)
273
+ key.set_factors(p, q)
274
+ else
275
+ key.p = p
276
+ key.q = q
277
+ end
278
+ if key.respond_to?(:set_crt_params)
279
+ key.set_crt_params(dmp1, dmq1, iqmp)
280
+ else
281
+ key.dmp1 = dmp1
282
+ key.dmq1 = dmq1
283
+ key.iqmp = iqmp
284
+ end
285
+ key
286
+ else
287
+ raise Exception, "Cannot decode private key of type #{type}"
288
+ end
289
+ end
290
+
252
291
  # Read a keyblob of the given type from the buffer, and return it as
253
292
  # a key. Only RSA, DSA, and ECDSA keys are supported.
254
293
  def read_keyblob(type)
@@ -283,15 +322,7 @@ module Net
283
322
  Net::SSH::Authentication::ED25519Loader.raiseUnlessLoaded("unsupported key type `#{type}'")
284
323
  key = Net::SSH::Authentication::ED25519::PubKey.read_keyblob(self)
285
324
  when /^ecdsa\-sha2\-(\w*)$/
286
- unless defined?(OpenSSL::PKey::EC)
287
- raise NotImplementedError, "unsupported key type `#{type}'"
288
- else
289
- begin
290
- key = OpenSSL::PKey::EC.read_keyblob($1, self)
291
- rescue OpenSSL::PKey::ECError
292
- raise NotImplementedError, "unsupported key type `#{type}'"
293
- end
294
- end
325
+ key = OpenSSL::PKey::EC.read_keyblob($1, self)
295
326
  else
296
327
  raise NotImplementedError, "unsupported key type `#{type}'"
297
328
  end
@@ -1,6 +1,5 @@
1
1
  require 'net/ssh/buffer'
2
2
  require 'net/ssh/loggable'
3
- require 'net/ssh/ruby_compat'
4
3
 
5
4
  module Net
6
5
  module SSH
@@ -11,6 +11,7 @@ module Net
11
11
  #
12
12
  # * ChallengeResponseAuthentication => maps to the :auth_methods option challenge-response (then coleasced into keyboard-interactive)
13
13
  # * KbdInteractiveAuthentication => maps to the :auth_methods keyboard-interactive
14
+ # * CertificateFile => maps to the :keycerts option
14
15
  # * Ciphers => maps to the :encryption option
15
16
  # * Compression => :compression
16
17
  # * CompressionLevel => :compression_level
@@ -22,7 +23,9 @@ module Net
22
23
  # * HostKeyAlias => :host_key_alias
23
24
  # * HostName => :host_name
24
25
  # * IdentityFile => maps to the :keys option
26
+ # * IdentityAgent => :identity_agent
25
27
  # * IdentitiesOnly => :keys_only
28
+ # * CheckHostIP => :check_host_ip
26
29
  # * Macs => maps to the :hmac option
27
30
  # * PasswordAuthentication => maps to the :auth_methods option password
28
31
  # * Port => :port
@@ -31,6 +34,7 @@ module Net
31
34
  # * ProxyJump => maps to the :proxy option
32
35
  # * PubKeyAuthentication => maps to the :auth_methods option
33
36
  # * RekeyLimit => :rekey_limit
37
+ # * StrictHostKeyChecking => :strict_host_key_checking
34
38
  # * User => :user
35
39
  # * UserKnownHostsFile => :user_known_hosts_file
36
40
  # * NumberOfPasswordPrompts => :number_of_password_prompts
@@ -95,7 +99,7 @@ module Net
95
99
  next if value.nil?
96
100
 
97
101
  key.downcase!
98
- value = $1 if value =~ /^"(.*)"$/
102
+ value = unquote(value)
99
103
 
100
104
  value = case value.strip
101
105
  when /^\d+$/ then value.to_i
@@ -126,7 +130,7 @@ module Net
126
130
  block_seen = true
127
131
  elsif !block_seen
128
132
  case key
129
- when 'identityfile'
133
+ when 'identityfile', 'certificatefile'
130
134
  (globals[key] ||= []) << value
131
135
  when 'include'
132
136
  included_file_paths(base_dir, value).each do |file_path|
@@ -137,7 +141,7 @@ module Net
137
141
  end
138
142
  elsif block_matched
139
143
  case key
140
- when 'identityfile'
144
+ when 'identityfile', 'certificatefile'
141
145
  (settings[key] ||= []) << value
142
146
  when 'include'
143
147
  included_file_paths(base_dir, value).each do |file_path|
@@ -147,11 +151,18 @@ module Net
147
151
  settings[key] = value unless settings.key?(key)
148
152
  end
149
153
  end
154
+
155
+ # ProxyCommand and ProxyJump override each other so they need to be tracked togeather
156
+ %w[proxyjump proxycommand].each do |proxy_key|
157
+ if (proxy_value = settings.delete(proxy_key))
158
+ settings['proxy'] ||= [proxy_key, proxy_value]
159
+ end
160
+ end
150
161
  end
151
162
 
152
163
  globals.merge(settings) do |key, oldval, newval|
153
164
  case key
154
- when 'identityfile'
165
+ when 'identityfile', 'certificatefile'
155
166
  oldval + newval
156
167
  else
157
168
  newval
@@ -186,22 +197,26 @@ module Net
186
197
 
187
198
  private
188
199
 
200
+ TRANSLATE_CONFIG_KEY_RENAME_MAP = {
201
+ bindaddress: :bind_address,
202
+ compression: :compression,
203
+ compressionlevel: :compression_level,
204
+ certificatefile: :keycerts,
205
+ connecttimeout: :timeout,
206
+ forwardagent: :forward_agent,
207
+ identitiesonly: :keys_only,
208
+ identityagent: :identity_agent,
209
+ globalknownhostsfile: :global_known_hosts_file,
210
+ hostkeyalias: :host_key_alias,
211
+ identityfile: :keys,
212
+ fingerprinthash: :fingerprint_hash,
213
+ port: :port,
214
+ stricthostkeychecking: :strict_host_key_checking,
215
+ user: :user,
216
+ userknownhostsfile: :user_known_hosts_file,
217
+ checkhostip: :check_host_ip
218
+ }.freeze
189
219
  def translate_config_key(hash, key, value, settings)
190
- rename = {
191
- bindaddress: :bind_address,
192
- compression: :compression,
193
- compressionlevel: :compression_level,
194
- connecttimeout: :timeout,
195
- forwardagent: :forward_agent,
196
- identitiesonly: :keys_only,
197
- globalknownhostsfile: :global_known_hosts_file,
198
- hostkeyalias: :host_key_alias,
199
- identityfile: :keys,
200
- fingerprinthash: :fingerprint_hash,
201
- port: :port,
202
- user: :user,
203
- userknownhostsfile: :user_known_hosts_file
204
- }
205
220
  case key
206
221
  when :ciphers
207
222
  hash[:encryption] = value.split(/,/)
@@ -246,15 +261,9 @@ module Net
246
261
  end
247
262
  when :preferredauthentications
248
263
  hash[:auth_methods] = value.split(/,/) # TODO we should place to preferred_auth_methods rather than auth_methods
249
- when :proxycommand
250
- if value and value !~ /^none$/
251
- require 'net/ssh/proxy/command'
252
- hash[:proxy] = Net::SSH::Proxy::Command.new(value)
253
- end
254
- when :proxyjump
255
- if value
256
- require 'net/ssh/proxy/jump'
257
- hash[:proxy] = Net::SSH::Proxy::Jump.new(value)
264
+ when :proxy
265
+ if (proxy = setup_proxy(*value))
266
+ hash[:proxy] = proxy
258
267
  end
259
268
  when :pubkeyauthentication
260
269
  if value
@@ -267,10 +276,25 @@ module Net
267
276
  when :sendenv
268
277
  multi_send_env = value.to_s.split(/\s+/)
269
278
  hash[:send_env] = multi_send_env.map { |e| Regexp.new pattern2regex(e).source, false }
279
+ when :setenv
280
+ hash[:set_env] = Shellwords.split(value.to_s).map { |e| e.split '=', 2 }.to_h
270
281
  when :numberofpasswordprompts
271
282
  hash[:number_of_password_prompts] = value.to_i
272
- when *rename.keys
273
- hash[rename[key]] = value
283
+ when *TRANSLATE_CONFIG_KEY_RENAME_MAP.keys
284
+ hash[TRANSLATE_CONFIG_KEY_RENAME_MAP[key]] = value
285
+ end
286
+ end
287
+
288
+ def setup_proxy(type, value)
289
+ case type
290
+ when 'proxycommand'
291
+ if value !~ /^none$/
292
+ require 'net/ssh/proxy/command'
293
+ Net::SSH::Proxy::Command.new(value)
294
+ end
295
+ when 'proxyjump'
296
+ require 'net/ssh/proxy/jump'
297
+ Net::SSH::Proxy::Jump.new(value)
274
298
  end
275
299
  end
276
300
 
@@ -326,12 +350,17 @@ module Net
326
350
  end
327
351
 
328
352
  def eval_match_conditions(condition, host, settings)
329
- conditions = condition.split(/\s+/)
353
+ # Not using `\s` for whitespace matching as canonical
354
+ # ssh_config parser implementation (OpenSSH) has specific character set.
355
+ # Ref: https://github.com/openssh/openssh-portable/blob/2581333d564d8697837729b3d07d45738eaf5a54/misc.c#L237-L239
356
+ conditions = condition.split(/[ \t\r\n]+|(?<!=)=(?!=)/).reject(&:empty?)
330
357
  return true if conditions == ["all"]
331
358
 
332
359
  conditions = conditions.each_slice(2)
333
- matching = true
360
+ condition_matches = []
334
361
  conditions.each do |(kind,exprs)|
362
+ exprs = unquote(exprs)
363
+
335
364
  case kind.downcase
336
365
  when "all"
337
366
  raise "all cannot be mixed with other conditions"
@@ -346,15 +375,19 @@ module Net
346
375
  exprs.split(",").each do |expr|
347
376
  condition_met = condition_met || host =~ pattern2regex(expr)
348
377
  end
349
- matching = matching && negated ^ condition_met
378
+ condition_matches << (true && negated ^ condition_met)
350
379
  # else
351
380
  # warn "net-ssh: Unsupported expr in Match block: #{kind}"
352
381
  end
353
382
  end
354
- matching
383
+
384
+ !condition_matches.empty? && condition_matches.all?
385
+ end
386
+
387
+ def unquote(string)
388
+ string =~ /^"(.*)"$/ ? Regexp.last_match(1) : string
355
389
  end
356
390
  end
357
391
  end
358
-
359
392
  end
360
393
  end
@@ -2,8 +2,8 @@ require 'net/ssh/loggable'
2
2
  require 'net/ssh/connection/constants'
3
3
  require 'net/ssh/connection/term'
4
4
 
5
- module Net
6
- module SSH
5
+ module Net
6
+ module SSH
7
7
  module Connection
8
8
 
9
9
  # The channel abstraction. Multiple "channels" can be multiplexed onto a
@@ -530,6 +530,7 @@ module Net
530
530
  @remote_maximum_packet_size = max_packet
531
531
  connection.forward.agent(self) if connection.options[:forward_agent] && type == "session"
532
532
  forward_local_env(connection.options[:send_env]) if connection.options[:send_env]
533
+ set_remote_env(connection.options[:set_env]) if connection.options[:set_env]
533
534
  @on_confirm_open.call(self) if @on_confirm_open
534
535
  end
535
536
 
@@ -648,10 +649,14 @@ module Net
648
649
  def update_local_window_size(size)
649
650
  @local_window_size -= size
650
651
  if local_window_size < local_maximum_window_size / 2
651
- connection.send_message(Buffer.from(:byte, CHANNEL_WINDOW_ADJUST,
652
- :long, remote_id, :long, LOCAL_WINDOW_SIZE_INCREMENT))
652
+ connection.send_message(
653
+ Buffer.from(:byte, CHANNEL_WINDOW_ADJUST, :long, remote_id, :long, LOCAL_WINDOW_SIZE_INCREMENT)
654
+ )
653
655
  @local_window_size += LOCAL_WINDOW_SIZE_INCREMENT
654
- @local_maximum_window_size += LOCAL_WINDOW_SIZE_INCREMENT if @local_maximum_window_size < @local_window_size || @local_maximum_window_size < GOOD_LOCAL_MAXIUMUM_WINDOW_SIZE
656
+
657
+ if @local_maximum_window_size < @local_window_size || @local_maximum_window_size < GOOD_LOCAL_MAXIUMUM_WINDOW_SIZE
658
+ @local_maximum_window_size += LOCAL_WINDOW_SIZE_INCREMENT
659
+ end
655
660
  end
656
661
  end
657
662
 
@@ -673,6 +678,13 @@ module Net
673
678
  end
674
679
  end
675
680
  end
681
+
682
+ # Set a +Hash+ of environment variables in the remote process' environment.
683
+ #
684
+ # channel.set_remote_env foo: 'bar', baz: 'whale'
685
+ def set_remote_env(env)
686
+ env.each { |key, value| self.env(key, value) }
687
+ end
676
688
  end
677
689
 
678
690
  end
@@ -1,5 +1,4 @@
1
1
  require 'net/ssh/loggable'
2
- require 'net/ssh/ruby_compat'
3
2
 
4
3
  module Net
5
4
  module SSH
@@ -1,5 +1,4 @@
1
1
  require 'net/ssh/loggable'
2
- require 'net/ssh/ruby_compat'
3
2
  require 'net/ssh/connection/channel'
4
3
  require 'net/ssh/connection/constants'
5
4
  require 'net/ssh/service/forward'
@@ -580,8 +579,10 @@ module Net
580
579
  info { "global request received: #{packet[:request_type]} #{packet[:want_reply]}" }
581
580
  callback = @on_global_request[packet[:request_type]]
582
581
  result = callback ? callback.call(packet[:request_data], packet[:want_reply]) : false
583
-
584
- raise "expected global request handler for `#{packet[:request_type]}' to return true, false, or :sent, but got #{result.inspect}" if result != :sent && result != true && result != false
582
+
583
+ if result != :sent && result != true && result != false
584
+ raise "expected global request handler for `#{packet[:request_type]}' to return true, false, or :sent, but got #{result.inspect}"
585
+ end
585
586
 
586
587
  if packet[:want_reply] && result != :sent
587
588
  msg = Buffer.from(:byte, result ? REQUEST_SUCCESS : REQUEST_FAILURE)
@@ -624,7 +625,9 @@ module Net
624
625
  failure = [err.code, err.reason]
625
626
  else
626
627
  channels[local_id] = channel
627
- msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long, channel.local_id, :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size)
628
+ msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long,
629
+ channel.local_id, :long, channel.local_maximum_window_size, :long,
630
+ channel.local_maximum_packet_size)
628
631
  end
629
632
  else
630
633
  failure = [3, "unknown channel type #{channel.type}"]
@@ -18,14 +18,12 @@ module Net
18
18
  class KeyFactory
19
19
  # Specifies the mapping of SSH names to OpenSSL key classes.
20
20
  MAP = {
21
- "dh" => OpenSSL::PKey::DH,
22
- "rsa" => OpenSSL::PKey::RSA,
23
- "dsa" => OpenSSL::PKey::DSA
21
+ 'dh' => OpenSSL::PKey::DH,
22
+ 'rsa' => OpenSSL::PKey::RSA,
23
+ 'dsa' => OpenSSL::PKey::DSA,
24
+ 'ecdsa' => OpenSSL::PKey::EC
24
25
  }
25
- if defined?(OpenSSL::PKey::EC)
26
- MAP["ecdsa"] = OpenSSL::PKey::EC
27
- MAP["ed25519"] = Net::SSH::Authentication::ED25519::PrivKey if defined? Net::SSH::Authentication::ED25519
28
- end
26
+ MAP["ed25519"] = Net::SSH::Authentication::ED25519::PrivKey if defined? Net::SSH::Authentication::ED25519
29
27
 
30
28
  class <<self
31
29
  # Fetch an OpenSSL key instance by its SSH name. It will be a new,
@@ -50,16 +48,17 @@ module Net
50
48
  # encrypted (requiring a passphrase to use), the user will be
51
49
  # prompted to enter their password unless passphrase works.
52
50
  def load_data_private_key(data, passphrase=nil, ask_passphrase=true, filename="", prompt=Prompt.default)
53
- key_read, error_classes = classify_key(data, filename)
51
+ key_type = classify_key(data, filename)
54
52
 
55
- encrypted_key = data.match(/ENCRYPTED/)
53
+ encrypted_key = nil
56
54
  tries = 0
57
55
 
58
56
  prompter = nil
59
57
  result =
60
58
  begin
61
- key_read[data, passphrase || 'invalid']
62
- rescue *error_classes
59
+ key_type.read(data, passphrase || 'invalid')
60
+ rescue *key_type.error_classes => e
61
+ encrypted_key = !!key_type.encrypted_key?(data, e) if encrypted_key.nil?
63
62
  if encrypted_key && ask_passphrase
64
63
  tries += 1
65
64
  if tries <= 3
@@ -106,20 +105,108 @@ module Net
106
105
 
107
106
  private
108
107
 
108
+ # rubocop:disable Style/Documentation, Lint/DuplicateMethods
109
+ class KeyType
110
+ def self.read(key_data, passphrase)
111
+ raise Exception, "TODO subclasses should implement read"
112
+ end
113
+
114
+ def self.error_classes
115
+ raise Exception, "TODO subclasses should implement read"
116
+ end
117
+
118
+ def self.encrypted_key?(data, error)
119
+ raise Exception, "TODO subclasses should implement is_encrypted_key"
120
+ end
121
+ end
122
+
123
+ class OpenSSHPrivateKeyType < KeyType
124
+ def self.read(key_data, passphrase)
125
+ Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader.read(key_data, passphrase)
126
+ end
127
+
128
+ def self.error_classes
129
+ [Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader::DecryptError]
130
+ end
131
+
132
+ def self.encrypted_key?(key_data, decode_error)
133
+ decode_error.is_a?(Net::SSH::Authentication::ED25519::OpenSSHPrivateKeyLoader::DecryptError) && decode_error.encrypted_key?
134
+ end
135
+ end
136
+
137
+ class OpenSSLKeyTypeBase < KeyType
138
+ def self.open_ssl_class
139
+ raise Exception, "TODO: subclasses should implement"
140
+ end
141
+
142
+ def self.read(key_data, passphrase)
143
+ open_ssl_class.new(key_data, passphrase)
144
+ end
145
+
146
+ def self.encrypted_key?(key_data, error)
147
+ key_data.match(/ENCRYPTED/)
148
+ end
149
+ end
150
+
151
+ class OpenSSLPKeyType < OpenSSLKeyTypeBase
152
+ def self.read(key_data, passphrase)
153
+ open_ssl_class.read(key_data, passphrase)
154
+ end
155
+
156
+ def self.open_ssl_class
157
+ OpenSSL::PKey
158
+ end
159
+
160
+ def self.error_classes
161
+ [ArgumentError, OpenSSL::PKey::PKeyError]
162
+ end
163
+ end
164
+
165
+ class OpenSSLDSAKeyType < OpenSSLKeyTypeBase
166
+ def self.open_ssl_class
167
+ OpenSSL::PKey::DSA
168
+ end
169
+
170
+ def self.error_classes
171
+ [OpenSSL::PKey::DSAError]
172
+ end
173
+ end
174
+
175
+ class OpenSSLRSAKeyType < OpenSSLKeyTypeBase
176
+ def self.open_ssl_class
177
+ OpenSSL::PKey::RSA
178
+ end
179
+
180
+ def self.error_classes
181
+ [OpenSSL::PKey::RSAError]
182
+ end
183
+ end
184
+
185
+ class OpenSSLECKeyType < OpenSSLKeyTypeBase
186
+ def self.open_ssl_class
187
+ OpenSSL::PKey::EC
188
+ end
189
+
190
+ def self.error_classes
191
+ [OpenSSL::PKey::ECError]
192
+ end
193
+ end
194
+ # rubocop:enable Style/Documentation, Lint/DuplicateMethods
195
+
109
196
  # Determine whether the file describes an RSA or DSA key, and return how load it
110
197
  # appropriately.
111
198
  def classify_key(data, filename)
112
199
  if data.match(/-----BEGIN OPENSSH PRIVATE KEY-----/)
113
200
  Net::SSH::Authentication::ED25519Loader.raiseUnlessLoaded("OpenSSH keys only supported if ED25519 is available")
114
- return ->(key_data, passphrase) { Net::SSH::Authentication::ED25519::PrivKey.read(key_data, passphrase) }, [ArgumentError]
201
+ return OpenSSHPrivateKeyType
115
202
  elsif OpenSSL::PKey.respond_to?(:read)
116
- return ->(key_data, passphrase) { OpenSSL::PKey.read(key_data, passphrase) }, [ArgumentError, OpenSSL::PKey::PKeyError]
203
+ return OpenSSLPKeyType
117
204
  elsif data.match(/-----BEGIN DSA PRIVATE KEY-----/)
118
- return ->(key_data, passphrase) { OpenSSL::PKey::DSA.new(key_data, passphrase) }, [OpenSSL::PKey::DSAError]
205
+ return OpenSSLDSAKeyType
119
206
  elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/)
120
- return ->(key_data, passphrase) { OpenSSL::PKey::RSA.new(key_data, passphrase) }, [OpenSSL::PKey::RSAError]
121
- elsif data.match(/-----BEGIN EC PRIVATE KEY-----/) && defined?(OpenSSL::PKey::EC)
122
- return ->(key_data, passphrase) { OpenSSL::PKey::EC.new(key_data, passphrase) }, [OpenSSL::PKey::ECError]
207
+ return OpenSSLRSAKeyType
208
+ elsif data.match(/-----BEGIN EC PRIVATE KEY-----/)
209
+ return OpenSSLECKeyType
123
210
  elsif data.match(/-----BEGIN (.+) PRIVATE KEY-----/)
124
211
  raise OpenSSL::PKey::PKeyError, "not a supported key type '#{$1}'"
125
212
  else