net-ssh 4.0.0.alpha3 → 4.0.0.alpha4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|