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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +8 -2
- data/.rubocop_todo.yml +392 -379
- data/.travis.yml +22 -22
- data/CHANGES.txt +63 -0
- data/Manifest +0 -1
- data/README.md +287 -0
- data/Rakefile +1 -2
- data/appveyor.yml +4 -2
- data/lib/net/ssh.rb +13 -4
- data/lib/net/ssh/authentication/agent.rb +9 -3
- data/lib/net/ssh/authentication/certificate.rb +10 -1
- data/lib/net/ssh/authentication/ed25519.rb +75 -46
- data/lib/net/ssh/authentication/ed25519_loader.rb +1 -1
- data/lib/net/ssh/authentication/key_manager.rb +35 -6
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +3 -1
- data/lib/net/ssh/authentication/methods/publickey.rb +2 -0
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +0 -1
- data/lib/net/ssh/authentication/session.rb +9 -6
- data/lib/net/ssh/buffer.rb +41 -10
- data/lib/net/ssh/buffered_io.rb +0 -1
- data/lib/net/ssh/config.rb +68 -35
- data/lib/net/ssh/connection/channel.rb +17 -5
- data/lib/net/ssh/connection/event_loop.rb +0 -1
- data/lib/net/ssh/connection/session.rb +7 -4
- data/lib/net/ssh/key_factory.rb +104 -17
- data/lib/net/ssh/known_hosts.rb +41 -26
- data/lib/net/ssh/loggable.rb +2 -2
- data/lib/net/ssh/proxy/command.rb +0 -1
- data/lib/net/ssh/proxy/socks5.rb +0 -1
- data/lib/net/ssh/service/forward.rb +2 -1
- data/lib/net/ssh/test.rb +8 -7
- data/lib/net/ssh/test/extensions.rb +2 -0
- data/lib/net/ssh/transport/algorithms.rb +161 -105
- data/lib/net/ssh/transport/cipher_factory.rb +11 -27
- data/lib/net/ssh/transport/constants.rb +10 -6
- data/lib/net/ssh/transport/ctr.rb +1 -7
- data/lib/net/ssh/transport/hmac.rb +15 -13
- data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
- data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/kex.rb +14 -11
- data/lib/net/ssh/transport/kex/abstract.rb +123 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +1 -15
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +9 -118
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +0 -6
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +18 -79
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +5 -4
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +5 -4
- data/lib/net/ssh/transport/openssl.rb +106 -93
- data/lib/net/ssh/transport/packet_stream.rb +52 -20
- data/lib/net/ssh/transport/session.rb +26 -4
- data/lib/net/ssh/transport/state.rb +1 -1
- data/lib/net/ssh/verifiers/accept_new.rb +7 -0
- data/lib/net/ssh/verifiers/always.rb +4 -0
- data/lib/net/ssh/verifiers/never.rb +4 -0
- data/lib/net/ssh/version.rb +3 -3
- data/net-ssh-public_cert.pem +18 -19
- data/net-ssh.gemspec +9 -7
- metadata +56 -40
- metadata.gz.sig +0 -0
- data/Gemfile.noed25519.lock +0 -41
- data/README.rdoc +0 -169
- data/lib/net/ssh/ruby_compat.rb +0 -13
- data/support/arcfour_check.rb +0 -20
data/lib/net/ssh/buffer.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/net/ssh/buffered_io.rb
CHANGED
data/lib/net/ssh/config.rb
CHANGED
@@ -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 =
|
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 :
|
250
|
-
if
|
251
|
-
|
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 *
|
273
|
-
hash[
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
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
|
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
|
-
|
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,
|
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}"]
|
data/lib/net/ssh/key_factory.rb
CHANGED
@@ -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
|
-
|
22
|
-
|
23
|
-
|
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?
|
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
|
-
|
51
|
+
key_type = classify_key(data, filename)
|
54
52
|
|
55
|
-
encrypted_key =
|
53
|
+
encrypted_key = nil
|
56
54
|
tries = 0
|
57
55
|
|
58
56
|
prompter = nil
|
59
57
|
result =
|
60
58
|
begin
|
61
|
-
|
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
|
201
|
+
return OpenSSHPrivateKeyType
|
115
202
|
elsif OpenSSL::PKey.respond_to?(:read)
|
116
|
-
return
|
203
|
+
return OpenSSLPKeyType
|
117
204
|
elsif data.match(/-----BEGIN DSA PRIVATE KEY-----/)
|
118
|
-
return
|
205
|
+
return OpenSSLDSAKeyType
|
119
206
|
elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/)
|
120
|
-
return
|
121
|
-
elsif data.match(/-----BEGIN EC PRIVATE KEY-----/)
|
122
|
-
return
|
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
|