net-ssh 4.0.0.alpha3 → 4.0.0.alpha4
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 +1 -1
- data.tar.gz.sig +0 -0
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +1148 -0
- data/.travis.yml +26 -5
- data/CHANGES.txt +9 -0
- data/Gemfile.norbnacl +5 -0
- data/Gemfile.norbnacl.lock +40 -0
- data/README.rdoc +0 -2
- data/Rakefile +10 -0
- data/appveyor.yml +18 -0
- data/lib/net/ssh.rb +15 -2
- data/lib/net/ssh/authentication/agent/java_pageant.rb +1 -1
- data/lib/net/ssh/authentication/agent/socket.rb +5 -5
- data/lib/net/ssh/authentication/ed25519.rb +9 -14
- data/lib/net/ssh/authentication/key_manager.rb +3 -3
- data/lib/net/ssh/authentication/methods/abstract.rb +4 -0
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +10 -10
- data/lib/net/ssh/authentication/methods/password.rb +14 -3
- data/lib/net/ssh/authentication/session.rb +2 -1
- data/lib/net/ssh/config.rb +12 -1
- data/lib/net/ssh/connection/event_loop.rb +110 -0
- data/lib/net/ssh/connection/keepalive.rb +2 -2
- data/lib/net/ssh/connection/session.rb +57 -27
- data/lib/net/ssh/key_factory.rb +48 -50
- data/lib/net/ssh/prompt.rb +48 -77
- data/lib/net/ssh/service/forward.rb +1 -1
- data/lib/net/ssh/test/channel.rb +7 -0
- data/lib/net/ssh/test/packet.rb +18 -2
- data/lib/net/ssh/test/script.rb +16 -2
- data/lib/net/ssh/test/socket.rb +1 -1
- data/lib/net/ssh/transport/algorithms.rb +6 -0
- data/lib/net/ssh/transport/session.rb +1 -0
- data/lib/net/ssh/version.rb +1 -1
- data/net-ssh.gemspec +7 -5
- metadata +25 -6
- metadata.gz.sig +0 -0
- data/setup.rb +0 -1585
@@ -70,7 +70,8 @@ module Net; module SSH; module Authentication
|
|
70
70
|
|
71
71
|
debug { "trying #{name}" }
|
72
72
|
begin
|
73
|
-
|
73
|
+
auth_class = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join)
|
74
|
+
method = auth_class.new(self, key_manager: key_manager, password_prompt: options[:password_prompt])
|
74
75
|
rescue NameError
|
75
76
|
debug{"Mechanism #{name} was requested, but isn't a known type. Ignoring it."}
|
76
77
|
next
|
data/lib/net/ssh/config.rb
CHANGED
@@ -59,7 +59,7 @@ module Net; module SSH
|
|
59
59
|
# given +files+ (defaulting to the list of files returned by
|
60
60
|
# #default_files), translates the resulting hash into the options
|
61
61
|
# recognized by Net::SSH, and returns them.
|
62
|
-
def for(host, files=
|
62
|
+
def for(host, files=expandable_default_files)
|
63
63
|
translate(files.inject({}) { |settings, file|
|
64
64
|
load(file, host, settings)
|
65
65
|
})
|
@@ -239,6 +239,17 @@ module Net; module SSH
|
|
239
239
|
|
240
240
|
private
|
241
241
|
|
242
|
+
def expandable_default_files
|
243
|
+
default_files.keep_if do |path|
|
244
|
+
begin
|
245
|
+
File.expand_path(path)
|
246
|
+
true
|
247
|
+
rescue ArgumentError
|
248
|
+
false
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
242
253
|
# Converts an ssh_config pattern into a regex for matching against
|
243
254
|
# host names.
|
244
255
|
def pattern2regex(pattern)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'net/ssh/loggable'
|
2
|
+
require 'net/ssh/ruby_compat'
|
3
|
+
|
4
|
+
module Net; module SSH; module Connection
|
5
|
+
# EventLoop can be shared across multiple sessions
|
6
|
+
#
|
7
|
+
# one issue is with blocks passed to loop, etc.
|
8
|
+
# they should get current session as parameter, but in
|
9
|
+
# case you're using multiple sessions in an event loop it doesnt makes sense
|
10
|
+
# and we don't pass session.
|
11
|
+
class EventLoop
|
12
|
+
include Loggable
|
13
|
+
|
14
|
+
def initialize(logger=nil)
|
15
|
+
self.logger = logger
|
16
|
+
@sessions = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def register(session)
|
20
|
+
@sessions << session
|
21
|
+
end
|
22
|
+
|
23
|
+
# process until timeout
|
24
|
+
# if a block is given a session will be removed from loop
|
25
|
+
# if block returns false for that session
|
26
|
+
def process(wait = nil, &block)
|
27
|
+
return false unless ev_preprocess(&block)
|
28
|
+
|
29
|
+
ev_select_and_postprocess(wait)
|
30
|
+
end
|
31
|
+
|
32
|
+
# process the event loop but only for the sepcified session
|
33
|
+
def process_only(session, wait = nil)
|
34
|
+
orig_sessions = @sessions
|
35
|
+
begin
|
36
|
+
@sessions = [session]
|
37
|
+
return false unless ev_preprocess
|
38
|
+
ev_select_and_postprocess(wait)
|
39
|
+
ensure
|
40
|
+
@sessions = orig_sessions
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Call preprocess on each session. If block given and that
|
45
|
+
# block retuns false then we exit the processing
|
46
|
+
def ev_preprocess(&block)
|
47
|
+
return false if block_given? && !yield(self)
|
48
|
+
@sessions.each(&:ev_preprocess)
|
49
|
+
return false if block_given? && !yield(self)
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
|
53
|
+
def ev_select_and_postprocess(wait)
|
54
|
+
owners = {}
|
55
|
+
r = []
|
56
|
+
w = []
|
57
|
+
minwait = nil
|
58
|
+
@sessions.each do |session|
|
59
|
+
sr,sw,actwait = session.ev_do_calculate_rw_wait(wait)
|
60
|
+
minwait = actwait if actwait && (minwait.nil? || actwait < minwait)
|
61
|
+
r.push(*sr)
|
62
|
+
w.push(*sw)
|
63
|
+
sr.each { |ri| owners[ri] = session }
|
64
|
+
sw.each { |wi| owners[wi] = session }
|
65
|
+
end
|
66
|
+
|
67
|
+
readers, writers, = Net::SSH::Compat.io_select(r, w, nil, minwait)
|
68
|
+
|
69
|
+
fired_sessions = {}
|
70
|
+
|
71
|
+
readers.each do |reader|
|
72
|
+
session = owners[reader]
|
73
|
+
(fired_sessions[session] ||= {r: [],w: []})[:r] << reader
|
74
|
+
end if readers
|
75
|
+
writers.each do |writer|
|
76
|
+
session = owners[writer]
|
77
|
+
(fired_sessions[session] ||= {r: [],w: []})[:w] << writer
|
78
|
+
end if writers
|
79
|
+
|
80
|
+
fired_sessions.each do |s,rw|
|
81
|
+
s.ev_do_handle_events(rw[:r],rw[:w])
|
82
|
+
end
|
83
|
+
|
84
|
+
@sessions.each { |s| s.ev_do_postprocess(fired_sessions.key?(s)) }
|
85
|
+
true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# optimized version for a single session
|
90
|
+
class SingleSessionEventLoop < EventLoop
|
91
|
+
# Compatibility for original single session event loops:
|
92
|
+
# we call block with session as argument
|
93
|
+
def ev_preprocess(&block)
|
94
|
+
return false if block_given? && !yield(@sessions.first)
|
95
|
+
@sessions.each(&:ev_preprocess)
|
96
|
+
return false if block_given? && !yield(@sessions.first)
|
97
|
+
return true
|
98
|
+
end
|
99
|
+
|
100
|
+
def ev_select_and_postprocess(wait)
|
101
|
+
raise "Only one session expected" unless @sessions.count == 1
|
102
|
+
session = @sessions.first
|
103
|
+
sr,sw,actwait = session.ev_do_calculate_rw_wait(wait)
|
104
|
+
readers, writers, = Net::SSH::Compat.io_select(sr, sw, nil, actwait)
|
105
|
+
|
106
|
+
session.ev_do_handle_events(readers,writers)
|
107
|
+
session.ev_do_postprocess(!((readers.nil? || readers.empty?) && (writers.nil? || writers.empty?)))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end; end; end
|
@@ -33,8 +33,8 @@ class Keepalive
|
|
33
33
|
(options[:keepalive_maxcount] || 3).to_i
|
34
34
|
end
|
35
35
|
|
36
|
-
def send_as_needed(
|
37
|
-
return
|
36
|
+
def send_as_needed(was_events)
|
37
|
+
return if was_events
|
38
38
|
return unless should_send?
|
39
39
|
info { "sending keepalive #{@unresponded_keepalive_count}" }
|
40
40
|
|
@@ -4,6 +4,7 @@ require 'net/ssh/connection/channel'
|
|
4
4
|
require 'net/ssh/connection/constants'
|
5
5
|
require 'net/ssh/service/forward'
|
6
6
|
require 'net/ssh/connection/keepalive'
|
7
|
+
require 'net/ssh/connection/event_loop'
|
7
8
|
|
8
9
|
module Net; module SSH; module Connection
|
9
10
|
|
@@ -81,6 +82,9 @@ module Net; module SSH; module Connection
|
|
81
82
|
@max_win_size = (options.has_key?(:max_win_size) ? options[:max_win_size] : 0x20000)
|
82
83
|
|
83
84
|
@keepalive = Keepalive.new(self)
|
85
|
+
|
86
|
+
@event_loop = options[:event_loop] || SingleSessionEventLoop.new
|
87
|
+
@event_loop.register(self)
|
84
88
|
end
|
85
89
|
|
86
90
|
# Retrieves a custom property from this instance. This can be used to
|
@@ -186,6 +190,8 @@ module Net; module SSH; module Connection
|
|
186
190
|
# This will also cause all active channels to be processed once each (see
|
187
191
|
# Net::SSH::Connection::Channel#on_process).
|
188
192
|
#
|
193
|
+
# TODO revise example
|
194
|
+
#
|
189
195
|
# # process multiple Net::SSH connections in parallel
|
190
196
|
# connections = [
|
191
197
|
# Net::SSH.start("host1", ...),
|
@@ -203,13 +209,10 @@ module Net; module SSH; module Connection
|
|
203
209
|
# break if connections.empty?
|
204
210
|
# end
|
205
211
|
def process(wait=nil, &block)
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
readers, writers, = Net::SSH::Compat.io_select(r, w, nil, io_select_wait(wait))
|
211
|
-
|
212
|
-
postprocess(readers, writers)
|
212
|
+
@event_loop.process(wait, &block)
|
213
|
+
rescue
|
214
|
+
force_channel_cleanup_on_close if closed?
|
215
|
+
raise
|
213
216
|
end
|
214
217
|
|
215
218
|
# This is called internally as part of #process. It dispatches any
|
@@ -217,19 +220,38 @@ module Net; module SSH; module Connection
|
|
217
220
|
# for any active channels. If a block is given, it is invoked at the
|
218
221
|
# start of the method and again at the end, and if the block ever returns
|
219
222
|
# false, this method returns false. Otherwise, it returns true.
|
220
|
-
def preprocess
|
223
|
+
def preprocess(&block)
|
221
224
|
return false if block_given? && !yield(self)
|
222
|
-
|
223
|
-
channels.each { |id, channel| channel.process unless channel.local_closed? }
|
225
|
+
ev_preprocess(&block)
|
224
226
|
return false if block_given? && !yield(self)
|
225
227
|
return true
|
226
228
|
end
|
227
229
|
|
228
|
-
#
|
229
|
-
#
|
230
|
+
# Called by event loop to process available data before going to
|
231
|
+
# event multiplexing
|
232
|
+
def ev_preprocess(&block)
|
233
|
+
dispatch_incoming_packets
|
234
|
+
each_channel { |id, channel| channel.process unless channel.local_closed? }
|
235
|
+
end
|
236
|
+
|
237
|
+
# Returns the file descriptors the event loop should wait for read/write events,
|
238
|
+
# we also return the max wait
|
239
|
+
def ev_do_calculate_rw_wait(wait)
|
240
|
+
r = listeners.keys
|
241
|
+
w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? }
|
242
|
+
[r,w,io_select_wait(wait)]
|
243
|
+
end
|
244
|
+
|
245
|
+
# This is called internally as part of #process.
|
246
|
+
def postprocess(readers, writers)
|
247
|
+
ev_do_handle_events(readers, writers)
|
248
|
+
end
|
249
|
+
|
250
|
+
# It loops over the given arrays of reader IO's and writer IO's,
|
251
|
+
# processing them as needed, and
|
230
252
|
# then calls Net::SSH::Transport::Session#rekey_as_needed to allow the
|
231
253
|
# transport layer to rekey. Then returns true.
|
232
|
-
def
|
254
|
+
def ev_do_handle_events(readers, writers)
|
233
255
|
Array(readers).each do |reader|
|
234
256
|
if listeners[reader]
|
235
257
|
listeners[reader].call(reader)
|
@@ -244,11 +266,14 @@ module Net; module SSH; module Connection
|
|
244
266
|
Array(writers).each do |writer|
|
245
267
|
writer.send_pending
|
246
268
|
end
|
269
|
+
end
|
247
270
|
|
248
|
-
|
271
|
+
# calls Net::SSH::Transport::Session#rekey_as_needed to allow the
|
272
|
+
# transport layer to rekey
|
273
|
+
def ev_do_postprocess(was_events)
|
274
|
+
@keepalive.send_as_needed(was_events)
|
249
275
|
transport.rekey_as_needed
|
250
|
-
|
251
|
-
return true
|
276
|
+
true
|
252
277
|
end
|
253
278
|
|
254
279
|
# Send a global request of the given type. The +extra+ parameters must
|
@@ -330,7 +355,7 @@ module Net; module SSH; module Connection
|
|
330
355
|
open_channel do |channel|
|
331
356
|
channel.exec(command) do |ch, success|
|
332
357
|
raise "could not execute command: #{command.inspect}" unless success
|
333
|
-
|
358
|
+
|
334
359
|
channel.on_data do |ch2, data|
|
335
360
|
if block
|
336
361
|
block.call(ch2, :stdout, data)
|
@@ -472,6 +497,11 @@ module Net; module SSH; module Connection
|
|
472
497
|
|
473
498
|
private
|
474
499
|
|
500
|
+
# iterate channels with the posibility of callbacks opening new channels during the iteration
|
501
|
+
def each_channel(&block)
|
502
|
+
channels.dup.each(&block)
|
503
|
+
end
|
504
|
+
|
475
505
|
# Read all pending packets from the connection and dispatch them as
|
476
506
|
# appropriate. Returns as soon as there are no more pending packets.
|
477
507
|
def dispatch_incoming_packets
|
@@ -495,14 +525,18 @@ module Net; module SSH; module Connection
|
|
495
525
|
|
496
526
|
def force_channel_cleanup_on_close
|
497
527
|
channels.each do |id, channel|
|
498
|
-
channel
|
499
|
-
channel.close
|
500
|
-
|
501
|
-
cleanup_channel(channel)
|
502
|
-
channel.do_close
|
528
|
+
channel_closed(channel)
|
503
529
|
end
|
504
530
|
end
|
505
531
|
|
532
|
+
def channel_closed(channel)
|
533
|
+
channel.remote_closed!
|
534
|
+
channel.close
|
535
|
+
|
536
|
+
cleanup_channel(channel)
|
537
|
+
channel.do_close
|
538
|
+
end
|
539
|
+
|
506
540
|
# Invoked when a global request is received. The registered global
|
507
541
|
# request callback will be invoked, if one exists, and the necessary
|
508
542
|
# reply returned.
|
@@ -611,11 +645,7 @@ module Net; module SSH; module Connection
|
|
611
645
|
info { "channel_close: #{packet[:local_id]}" }
|
612
646
|
|
613
647
|
channel = channels[packet[:local_id]]
|
614
|
-
channel
|
615
|
-
channel.close
|
616
|
-
|
617
|
-
cleanup_channel(channel)
|
618
|
-
channel.do_close
|
648
|
+
channel_closed(channel)
|
619
649
|
end
|
620
650
|
|
621
651
|
def channel_success(packet)
|
data/lib/net/ssh/key_factory.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
require 'net/ssh/transport/openssl'
|
2
2
|
require 'net/ssh/prompt'
|
3
|
-
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'net/ssh/authentication/ed25519'
|
6
|
+
rescue Gem::LoadError => e # rubocop:disable Lint/HandleExceptions
|
7
|
+
end
|
4
8
|
|
5
9
|
module Net; module SSH
|
6
10
|
|
@@ -22,12 +26,10 @@ module Net; module SSH
|
|
22
26
|
}
|
23
27
|
if defined?(OpenSSL::PKey::EC)
|
24
28
|
MAP["ecdsa"] = OpenSSL::PKey::EC
|
25
|
-
MAP["ed25519"] = ED25519::PrivKey
|
29
|
+
MAP["ed25519"] = ED25519::PrivKey if defined? ED25519
|
26
30
|
end
|
27
31
|
|
28
32
|
class <<self
|
29
|
-
include Prompt
|
30
|
-
|
31
33
|
# Fetch an OpenSSL key instance by its SSH name. It will be a new,
|
32
34
|
# empty key of the given type.
|
33
35
|
def get(name)
|
@@ -39,9 +41,9 @@ module Net; module SSH
|
|
39
41
|
# appropriately. The new key is returned. If the key itself is
|
40
42
|
# encrypted (requiring a passphrase to use), the user will be
|
41
43
|
# prompted to enter their password unless passphrase works.
|
42
|
-
def load_private_key(filename, passphrase=nil, ask_passphrase=true)
|
44
|
+
def load_private_key(filename, passphrase=nil, ask_passphrase=true, prompt=Prompt.default)
|
43
45
|
data = File.read(File.expand_path(filename))
|
44
|
-
load_data_private_key(data, passphrase, ask_passphrase, filename)
|
46
|
+
load_data_private_key(data, passphrase, ask_passphrase, filename, prompt)
|
45
47
|
end
|
46
48
|
|
47
49
|
# Loads a private key. It will correctly determine
|
@@ -49,58 +51,32 @@ module Net; module SSH
|
|
49
51
|
# appropriately. The new key is returned. If the key itself is
|
50
52
|
# encrypted (requiring a passphrase to use), the user will be
|
51
53
|
# prompted to enter their password unless passphrase works.
|
52
|
-
def load_data_private_key(data, passphrase=nil, ask_passphrase=true, filename="")
|
53
|
-
|
54
|
-
pkey_read = true
|
55
|
-
error_class = ArgumentError
|
56
|
-
else
|
57
|
-
pkey_read = false
|
58
|
-
if data.match(/-----BEGIN DSA PRIVATE KEY-----/)
|
59
|
-
key_type = OpenSSL::PKey::DSA
|
60
|
-
error_class = OpenSSL::PKey::DSAError
|
61
|
-
elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/)
|
62
|
-
key_type = OpenSSL::PKey::RSA
|
63
|
-
error_class = OpenSSL::PKey::RSAError
|
64
|
-
elsif data.match(/-----BEGIN EC PRIVATE KEY-----/) && defined?(OpenSSL::PKey::EC)
|
65
|
-
key_type = OpenSSL::PKey::EC
|
66
|
-
error_class = OpenSSL::PKey::ECError
|
67
|
-
elsif data.match(/-----BEGIN OPENSSH PRIVATE KEY-----/)
|
68
|
-
openssh_key = true
|
69
|
-
key_type = ED25519::PrivKey
|
70
|
-
elsif data.match(/-----BEGIN (.+) PRIVATE KEY-----/)
|
71
|
-
raise OpenSSL::PKey::PKeyError, "not a supported key type '#{$1}'"
|
72
|
-
else
|
73
|
-
raise OpenSSL::PKey::PKeyError, "not a private key (#{filename})"
|
74
|
-
end
|
75
|
-
end
|
54
|
+
def load_data_private_key(data, passphrase=nil, ask_passphrase=true, filename="", prompt=Prompt.default)
|
55
|
+
key_read, error_class = classify_key(data, filename)
|
76
56
|
|
77
57
|
encrypted_key = data.match(/ENCRYPTED/)
|
78
|
-
openssh_key = data.match(/-----BEGIN OPENSSH PRIVATE KEY-----/)
|
79
58
|
tries = 0
|
80
59
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
passphrase = prompt("Enter passphrase for #{filename}:", false)
|
96
|
-
retry
|
60
|
+
prompter = nil
|
61
|
+
result =
|
62
|
+
begin
|
63
|
+
key_read[data, passphrase || 'invalid']
|
64
|
+
rescue error_class
|
65
|
+
if encrypted_key && ask_passphrase
|
66
|
+
tries += 1
|
67
|
+
if tries <= 3
|
68
|
+
prompter ||= prompt.start(type: 'private_key', filename: filename, sha: Digest::SHA256.digest(data))
|
69
|
+
passphrase = prompter.ask("Enter passphrase for #{filename}:", false)
|
70
|
+
retry
|
71
|
+
else
|
72
|
+
raise
|
73
|
+
end
|
97
74
|
else
|
98
75
|
raise
|
99
76
|
end
|
100
|
-
else
|
101
|
-
raise
|
102
77
|
end
|
103
|
-
|
78
|
+
prompter.success if prompter
|
79
|
+
result
|
104
80
|
end
|
105
81
|
|
106
82
|
# Loads a public key from a file. It will correctly determine whether
|
@@ -129,6 +105,28 @@ module Net; module SSH
|
|
129
105
|
reader = Net::SSH::Buffer.new(blob)
|
130
106
|
reader.read_key or raise OpenSSL::PKey::PKeyError, "not a public key #{filename.inspect}"
|
131
107
|
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Determine whether the file describes an RSA or DSA key, and return how load it
|
112
|
+
# appropriately.
|
113
|
+
def classify_key(data, filename)
|
114
|
+
if data.match(/-----BEGIN OPENSSH PRIVATE KEY-----/)
|
115
|
+
return ->(key_data, passphrase) { ED25519::PrivKey.read(key_data, passphrase) }, ArgumentError
|
116
|
+
elsif OpenSSL::PKey.respond_to?(:read)
|
117
|
+
return ->(key_data, passphrase) { OpenSSL::PKey.read(key_data, passphrase) }, ArgumentError
|
118
|
+
elsif data.match(/-----BEGIN DSA PRIVATE KEY-----/)
|
119
|
+
return ->(key_data, passphrase) { OpenSSL::PKey::DSA.new(key_data, passphrase) }, OpenSSL::PKey::DSAError
|
120
|
+
elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/)
|
121
|
+
return ->(key_data, passphrase) { OpenSSL::PKey::RSA.new(key_data, passphrase) }, OpenSSL::PKey::RSAError
|
122
|
+
elsif data.match(/-----BEGIN EC PRIVATE KEY-----/) && defined?(OpenSSL::PKey::EC)
|
123
|
+
return ->(key_data, passphrase) { OpenSSL::PKey::EC.new(key_data, passphrase) }, OpenSSL::PKey::ECError
|
124
|
+
elsif data.match(/-----BEGIN (.+) PRIVATE KEY-----/)
|
125
|
+
raise OpenSSL::PKey::PKeyError, "not a supported key type '#{$1}'"
|
126
|
+
else
|
127
|
+
raise OpenSSL::PKey::PKeyError, "not a private key (#{filename})"
|
128
|
+
end
|
129
|
+
end
|
132
130
|
end
|
133
131
|
|
134
132
|
end
|