rye 0.3.2 → 0.4
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.
- data/CHANGES.txt +20 -0
- data/README.rdoc +68 -22
- data/bin/rye +131 -0
- data/bin/try +40 -17
- data/lib/rye.rb +150 -13
- data/lib/rye/box.rb +141 -125
- data/lib/rye/cmd.rb +23 -0
- data/lib/rye/key.rb +134 -0
- data/lib/rye/rap.rb +9 -0
- data/lib/rye/set.rb +9 -0
- data/rye.gemspec +14 -3
- data/try/copying.rb +19 -0
- data/try/keys.rb +139 -0
- data/tst/10-key1 +27 -0
- data/tst/10-key1.pub +1 -0
- data/tst/10-key2 +30 -0
- data/tst/10-key2.pub +1 -0
- data/tst/10_keys_test.rb +88 -0
- data/{test/10_rye_test.rb → tst/50_rye_test.rb} +11 -20
- metadata +34 -5
data/lib/rye/box.rb
CHANGED
@@ -33,7 +33,9 @@ module Rye
|
|
33
33
|
attr_accessor :safe
|
34
34
|
attr_accessor :opts
|
35
35
|
|
36
|
-
|
36
|
+
# The most recent value from Box.cd or Box.[]
|
37
|
+
attr_reader :current_working_directory
|
38
|
+
|
37
39
|
# * +host+ The hostname to connect to. The default is localhost.
|
38
40
|
# * +opts+ a hash of optional arguments.
|
39
41
|
#
|
@@ -92,16 +94,17 @@ module Rye
|
|
92
94
|
alias :commands :can
|
93
95
|
alias :cmds :can
|
94
96
|
|
95
|
-
|
97
|
+
|
98
|
+
|
96
99
|
# Change the current working directory (sort of).
|
97
100
|
#
|
98
101
|
# I haven't been able to wrangle Net::SSH to do my bidding.
|
99
102
|
# "My bidding" in this case, is maintaining an open channel between commands.
|
100
|
-
# I'm using Net::SSH::Connection::Session#exec
|
103
|
+
# I'm using Net::SSH::Connection::Session#exec for all commands
|
101
104
|
# which is like a funky helper method that opens a new channel
|
102
105
|
# each time it's called. This seems to be okay for one-off
|
103
106
|
# commands but changing the directory only works for the channel
|
104
|
-
# it's executed in. The next time exec
|
107
|
+
# it's executed in. The next time exec is called, there's a
|
105
108
|
# new channel which is back in the default (home) directory.
|
106
109
|
#
|
107
110
|
# Long story short, the work around is to maintain the current
|
@@ -117,7 +120,6 @@ module Rye
|
|
117
120
|
end
|
118
121
|
alias :cd :'[]'
|
119
122
|
|
120
|
-
|
121
123
|
# Open an SSH session with +@host+. This called automatically
|
122
124
|
# when you the first comamnd is run if it's not already connected.
|
123
125
|
# Raises a Rye::NoHost exception if +@host+ is not specified.
|
@@ -163,148 +165,162 @@ module Rye
|
|
163
165
|
Rye.keys
|
164
166
|
end
|
165
167
|
|
168
|
+
# Returns +@host+
|
169
|
+
def to_s
|
170
|
+
@host
|
171
|
+
end
|
166
172
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
#
|
173
|
-
# The command is searched for in the local PATH (where
|
174
|
-
# Rye is running). An exception is raised if it's not
|
175
|
-
# found. NOTE: Because this happens locally, you won't
|
176
|
-
# want to use this method if the environment is quite
|
177
|
-
# different from the remote machine it will be executed
|
178
|
-
# on.
|
179
|
-
#
|
180
|
-
# The command arguments are passed through Escape.shell_command
|
181
|
-
# (that means you can't use environment variables or asterisks).
|
182
|
-
#
|
183
|
-
def Box.prepare_command(cmd, *args)
|
184
|
-
args &&= [args].flatten.compact
|
185
|
-
cmd = Rye::Box.which(cmd)
|
186
|
-
raise CommandNotFound.new(cmd || 'nil') unless cmd
|
187
|
-
Rye::Box.escape(@safe, cmd, *args)
|
173
|
+
def inspect
|
174
|
+
%q{#<%s:%s cwd=%s env=%s safe=%s opts=%s>} %
|
175
|
+
[self.class.to_s, self.host,
|
176
|
+
@current_working_directory, (@current_environment_variables || '').inspect,
|
177
|
+
self.safe, self.opts.inspect]
|
188
178
|
end
|
189
179
|
|
190
|
-
#
|
191
|
-
#
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
next unless File.exists? path # occurrences of shortname
|
201
|
-
Dir.new(path).entries.member?(shortname) # found in the paths.
|
202
|
-
end
|
203
|
-
File.join(dir.first, shortname) unless dir.empty? # Return just the first
|
180
|
+
# Compares itself with the +other+ box. If the hostnames
|
181
|
+
# are the same, this will return true. Otherwise false.
|
182
|
+
def ==(other)
|
183
|
+
@host == other.host
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns the host SSH keys for this box
|
187
|
+
def host_key
|
188
|
+
raise "No host" unless @host
|
189
|
+
Rye.remote_host_keys(@host)
|
204
190
|
end
|
205
191
|
|
206
|
-
#
|
207
|
-
#
|
208
|
-
#
|
209
|
-
# * +args+ Array of arguments to be sent to the command. Each element
|
210
|
-
# is one argument:. i.e. <tt>['-l', 'some/path']</tt>
|
192
|
+
# Copy the local public keys (as specified by Rye.keys) to
|
193
|
+
# this box into ~/.ssh/authorized_keys and ~/.ssh/authorized_keys2.
|
194
|
+
# Returns an Array of the private keys files used to generate the public keys.
|
211
195
|
#
|
212
|
-
# NOTE:
|
213
|
-
# you can only use literal values. That means no asterisks too.
|
196
|
+
# NOTE: authorize_keys disables safe-mode for this box while it runs.
|
214
197
|
#
|
215
|
-
def
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
198
|
+
def authorize_keys
|
199
|
+
added_keys = []
|
200
|
+
opts[:safe] = false
|
201
|
+
Rye.keys.each do |key|
|
202
|
+
path = key[2]
|
203
|
+
debug "# Public key for #{path}"
|
204
|
+
k = Rye::Key.from_file(path).public_key.to_ssh2
|
205
|
+
self.mkdir('-p', '~/.ssh') # Silently create dir if it doesn't exist
|
206
|
+
self.echo("'#{k}' >> ~/.ssh/authorized_keys")
|
207
|
+
self.echo("'#{k}' >> ~/.ssh/authorized_keys2")
|
208
|
+
self.chmod('-R', '0600', '.ssh')
|
209
|
+
added_keys << path
|
210
|
+
end
|
211
|
+
opts[:safe] = true
|
212
|
+
added_keys
|
223
213
|
end
|
224
214
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
def
|
229
|
-
|
230
|
-
|
215
|
+
private
|
216
|
+
|
217
|
+
|
218
|
+
def debug(msg); @debug.puts msg if @debug; end
|
219
|
+
def error(msg); @error.puts msg if @error; end
|
220
|
+
|
221
|
+
|
222
|
+
# Add the current environment variables to the beginning of +cmd+
|
223
|
+
def prepend_env(cmd)
|
224
|
+
return cmd unless @current_environment_variables.is_a?(Hash)
|
225
|
+
env = ''
|
226
|
+
@current_environment_variables.each_pair do |n,v|
|
227
|
+
env << "export #{n}=#{Escape.shell_single_word(v)}; "
|
228
|
+
end
|
229
|
+
[env, cmd].join(' ')
|
231
230
|
end
|
232
231
|
|
233
|
-
private
|
234
|
-
|
235
|
-
|
236
|
-
def debug(msg); @debug.puts msg if @debug; end
|
237
|
-
def error(msg); @error.puts msg if @error; end
|
238
232
|
|
233
|
+
# Execute a command over SSH
|
234
|
+
#
|
235
|
+
# * +args+ is a command name and list of arguments.
|
236
|
+
# The command name is the literal name of the command
|
237
|
+
# that will be executed in the remote shell. The arguments
|
238
|
+
# will be thoroughly escaped and passed to the command.
|
239
|
+
#
|
240
|
+
# rbox = Rye::Box.new
|
241
|
+
# rbox.ls '-l', 'arg1', 'arg2'
|
242
|
+
#
|
243
|
+
# is equivalent to
|
244
|
+
#
|
245
|
+
# $ ls -l 'arg1' 'arg2'
|
246
|
+
#
|
247
|
+
# This method will try to connect to the host automatically
|
248
|
+
# but if it fails it will raise a Rye::NotConnected exception.
|
249
|
+
#
|
250
|
+
def run_command(*args)
|
251
|
+
connect if !@ssh || @ssh.closed?
|
252
|
+
args = args.flatten.compact
|
253
|
+
args = args.first.split(/\s+/) if args.size == 1
|
254
|
+
cmd = args.shift
|
239
255
|
|
240
|
-
#
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
env << "export #{n}=#{Escape.shell_single_word(v)}; "
|
246
|
-
end
|
247
|
-
[env, cmd].join(' ')
|
256
|
+
# Symbols to switches. :l -> -l, :help -> --help
|
257
|
+
args.collect! do |a|
|
258
|
+
a = "-#{a}" if a.is_a?(Symbol) && a.to_s.size == 1
|
259
|
+
a = "--#{a}" if a.is_a?(Symbol)
|
260
|
+
a
|
248
261
|
end
|
249
262
|
|
263
|
+
raise Rye::NotConnected, @host unless @ssh && !@ssh.closed?
|
250
264
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
# will be thoroughly escaped and passed to the command.
|
257
|
-
#
|
258
|
-
# rbox = Rye::Box.new
|
259
|
-
# rbox.ls '-l', 'arg1', 'arg2'
|
260
|
-
#
|
261
|
-
# is equivalent to
|
262
|
-
#
|
263
|
-
# $ ls -l 'arg1' 'arg2'
|
264
|
-
#
|
265
|
-
# This method will try to connect to the host automatically
|
266
|
-
# but if it fails it will raise a Rye::NotConnected exception.
|
267
|
-
#
|
268
|
-
def add_command(*args)
|
269
|
-
connect if !@ssh || @ssh.closed?
|
270
|
-
args = args.first.split(/\s+/) if args.size == 1
|
271
|
-
cmd, args = args.flatten.compact
|
272
|
-
|
273
|
-
raise Rye::NotConnected, @host unless @ssh && !@ssh.closed?
|
274
|
-
|
275
|
-
cmd_clean = Rye::Box.escape(@safe, cmd, args)
|
276
|
-
cmd_clean = prepend_env(cmd_clean)
|
277
|
-
if @current_working_directory
|
278
|
-
cwd = Rye::Box.escape(@safe, 'cd', @current_working_directory)
|
279
|
-
cmd_clean = [cwd, cmd_clean].join('; ')
|
280
|
-
end
|
281
|
-
debug "Executing: %s" % cmd_clean
|
282
|
-
stdout, stderr = net_ssh_exec! cmd_clean
|
283
|
-
rap = Rye::Rap.new(self)
|
284
|
-
rap.add_stdout(stdout || '')
|
285
|
-
rap.add_stderr(stderr || '')
|
286
|
-
rap
|
265
|
+
cmd_clean = Rye.escape(@safe, cmd, args)
|
266
|
+
cmd_clean = prepend_env(cmd_clean)
|
267
|
+
if @current_working_directory
|
268
|
+
cwd = Rye.escape(@safe, 'cd', @current_working_directory)
|
269
|
+
cmd_clean = [cwd, cmd_clean].join(' && ')
|
287
270
|
end
|
288
|
-
alias :cmd :add_command
|
289
271
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
272
|
+
debug "Executing: %s" % cmd_clean
|
273
|
+
stdout, stderr, ecode, esignal = net_ssh_exec! cmd_clean
|
274
|
+
rap = Rye::Rap.new(self)
|
275
|
+
rap.add_stdout(stdout || '')
|
276
|
+
rap.add_stderr(stderr || '')
|
277
|
+
rap.exit_code = ecode
|
278
|
+
rap.exit_signal = esignal
|
279
|
+
rap.cmd = cmd
|
280
|
+
|
281
|
+
raise Rye::CommandError.new(rap) if ecode > 0
|
282
|
+
|
283
|
+
rap
|
284
|
+
end
|
285
|
+
alias :cmd :run_command
|
286
|
+
|
287
|
+
# Executes +command+ via SSH
|
288
|
+
# Returns an Array with 4 elements: [stdout, stderr, exit code, exit signal]
|
289
|
+
def net_ssh_exec!(command)
|
290
|
+
block ||= Proc.new do |channel, type, data|
|
291
|
+
channel[:stdout] ||= ""
|
292
|
+
channel[:stderr] ||= ""
|
293
|
+
channel[:stdout] << data if type == :stdout
|
294
|
+
channel[:stderr] << data if type == :stderr
|
295
|
+
channel.on_request("exit-status") do |ch, data|
|
296
|
+
# Anything greater than 0 is an error
|
297
|
+
channel[:exit_code] = data.read_long
|
298
|
+
end
|
299
|
+
channel.on_request("exit-signal") do |ch, data|
|
300
|
+
# This should be the POSIX SIGNAL that ended the process
|
301
|
+
channel[:exit_signal] = data.read_long
|
299
302
|
end
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
303
|
+
# For long-running commands like top, this will print the output.
|
304
|
+
# It cool, but we'd also need to enable STDIN to interact with
|
305
|
+
# command.
|
306
|
+
#channel.on_data do |ch, data|
|
307
|
+
# puts "got stdout: #{data}"
|
308
|
+
# channel.send_data "something for stdin\n"
|
309
|
+
#end
|
305
310
|
end
|
306
311
|
|
312
|
+
channel = @ssh.exec(command, &block)
|
313
|
+
channel.wait # block until we get a response
|
307
314
|
|
315
|
+
channel[:exit_code] ||= 0
|
316
|
+
channel[:exit_code] &&= channel[:exit_code].to_i
|
317
|
+
|
318
|
+
channel[:stderr].gsub!(/bash: line \d+:\s+/, '') if channel[:stderr]
|
319
|
+
|
320
|
+
[channel[:stdout], channel[:stderr], channel[:exit_code], channel[:exit_signal]]
|
321
|
+
end
|
322
|
+
|
323
|
+
|
308
324
|
|
309
325
|
end
|
310
326
|
end
|
data/lib/rye/cmd.rb
CHANGED
@@ -43,16 +43,39 @@ module Rye;
|
|
43
43
|
def perl(*args); cmd('perl', args); end
|
44
44
|
def bash(*args); cmd('bash', args); end
|
45
45
|
def echo(*args); cmd('echo', args); end
|
46
|
+
def test(*args); cmd('test', args); end
|
46
47
|
|
47
48
|
def mount; cmd("mount"); end
|
48
49
|
def sleep(seconds=1); cmd("sleep", seconds); end
|
50
|
+
def mkdir(*args); cmd('mkdir', args); end
|
49
51
|
def touch(*args); cmd('touch', args); end
|
50
52
|
def uname(*args); cmd('uname', args); end
|
53
|
+
def chmod(*args); cmd('uname', args); end
|
51
54
|
|
52
55
|
def uptime; cmd("uptime"); end
|
53
56
|
def python(*args); cmd('python', args); end
|
54
57
|
def printenv(*args); cmd('printenv', args); end
|
55
58
|
|
59
|
+
|
60
|
+
# def copy_to(*boxes)
|
61
|
+
# p boxes
|
62
|
+
#
|
63
|
+
# @scp = Net::SCP.start(@host, @opts[:user], @opts || {})
|
64
|
+
# #@ssh.is_a?(Net::SSH::Connection::Session) && !@ssh.closed?
|
65
|
+
# p @scp
|
66
|
+
# end
|
67
|
+
|
68
|
+
|
69
|
+
#def copy_to(*args)
|
70
|
+
# args = [args].flatten.compact || []
|
71
|
+
# other = args.pop
|
72
|
+
# p other
|
73
|
+
#end
|
74
|
+
|
75
|
+
def exists?
|
76
|
+
cmd("uptime");
|
77
|
+
end
|
78
|
+
|
56
79
|
# Consider Rye.sysinfo.os == :unix
|
57
80
|
end
|
58
81
|
|
data/lib/rye/key.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
|
2
|
+
module Rye
|
3
|
+
class Key
|
4
|
+
class BadFile < RuntimeError
|
5
|
+
def initialize(m); @m = m; end
|
6
|
+
def message; "That ain't a no key. #{$/}#{@m}"; end
|
7
|
+
end
|
8
|
+
class BadPerm < RuntimeError
|
9
|
+
def initialize(m); @m = m; end
|
10
|
+
def message; "Bad file permissions. Set to 0600. #{$/}#{@m}"; end
|
11
|
+
end
|
12
|
+
|
13
|
+
# A nickname for this key. If a path was specified this defaults to the basename.
|
14
|
+
attr_reader :name
|
15
|
+
# Authentication type: RSA or DSA
|
16
|
+
attr_reader :authtype
|
17
|
+
# Key type: public or private
|
18
|
+
attr_reader :keytype
|
19
|
+
|
20
|
+
def initialize(data, name=nil)
|
21
|
+
@data = data
|
22
|
+
@name = name || 'default'
|
23
|
+
parse_data
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.generate_pkey(authtype="RSA", bits=1024)
|
27
|
+
unless Rye::Key.supported_authentication?(authtype)
|
28
|
+
raise OpenSSL::PKey::PKeyError, "Unknown authentication: #{authttype}"
|
29
|
+
end
|
30
|
+
bits &&= bits.to_i
|
31
|
+
klass = authtype.upcase == "RSA" ? OpenSSL::PKey::RSA : OpenSSL::PKey::DSA
|
32
|
+
pk = klass.new(bits)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.from_file(path)
|
36
|
+
raise BadFile, path unless File.exists?(path || '')
|
37
|
+
pkey = self.new File.read(path), File.basename(path)
|
38
|
+
file_perms = (File.stat(path).mode & 600)
|
39
|
+
raise BadPerm, path if file_perms != 0 && pkey.private?
|
40
|
+
pkey
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def sign(string, digesttype="sha1")
|
45
|
+
Rye::Key.sign(@keypair.to_s, string, digesttype)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.sign(secret, string, digesttype="sha1")
|
49
|
+
@@digest ||= {}
|
50
|
+
@@digest[digest] ||= OpenSSL::Digest::Digest.new(digesttype)
|
51
|
+
sig = OpenSSL::HMAC.hexdigest(@@digest[digest], secret, string).strip
|
52
|
+
end
|
53
|
+
def self.sign_aws(secret, string)
|
54
|
+
::Base64.encode64(self.sign(secret, string, "sha1")).strip
|
55
|
+
end
|
56
|
+
|
57
|
+
def private_key
|
58
|
+
raise OpenSSL::PKey::PKeyError, "No private key" if public? || !@keypair
|
59
|
+
@keypair.to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
def public_key
|
63
|
+
raise OpenSSL::PKey::PKeyError, "No public key" if !@keypair
|
64
|
+
pubkey = public? ? @keypair : @keypair.public_key
|
65
|
+
# Add the to_ssh2 method to the instance of OpenSSL::PKey::*SA only
|
66
|
+
def pubkey.to_ssh2; Rye::Key.public_key_to_ssh2(self); end
|
67
|
+
pubkey
|
68
|
+
end
|
69
|
+
|
70
|
+
# Encrypt +text+ with this public or private key. The key must
|
71
|
+
def encrypt(text); ::Base64.encode64(@keypair.send("#{keytype.downcase}_encrypt", text)); end
|
72
|
+
def decrypt(text); @keypair.send("#{keytype.downcase}_decrypt", ::Base64.decode64(text)); end
|
73
|
+
|
74
|
+
def private?; @keytype.upcase == "PRIVATE"; end
|
75
|
+
def public?; @keytype.upcase == "PUBLIC"; end
|
76
|
+
def rsa?; @authtype.upcase == "RSA"; end
|
77
|
+
def dsa?; @authtype.upcase == "DSA"; end
|
78
|
+
def encrypted?; @data && @data.match(/ENCRYPTED/); end
|
79
|
+
|
80
|
+
# * +pubkey+ an instance of OpenSSL::PKey::RSA or OpenSSL::PKey::DSA
|
81
|
+
# Returns a public key in SSH format (suitable for ~/.ssh/authorized_keys)
|
82
|
+
def self.public_key_to_ssh2(pubkey)
|
83
|
+
authtype = pubkey.class.to_s.split('::').last.downcase
|
84
|
+
b64pub = ::Base64.encode64(pubkey.to_blob).strip.gsub(/[\r\n]/, '')
|
85
|
+
"ssh-%s %s" % [authtype, b64pub] # => ssh-rsa AAAAB3NzaC1...=
|
86
|
+
end
|
87
|
+
|
88
|
+
def dump
|
89
|
+
puts @keypair.public_key.to_text
|
90
|
+
puts @keypair.public_key.to_pem
|
91
|
+
end
|
92
|
+
|
93
|
+
# Reveals the key basename. Does not print the key.
|
94
|
+
#
|
95
|
+
# <Rye::Key:id_rsa.pub>
|
96
|
+
#
|
97
|
+
def to_s
|
98
|
+
'<%s:%s>' % [self.class.to_s, name]
|
99
|
+
end
|
100
|
+
|
101
|
+
# Reveals some metadata about the key. Does not print the key.
|
102
|
+
#
|
103
|
+
# <Rye::Key:id_rsa.pub authtype="RSA" keytype="PRIVATE">
|
104
|
+
#
|
105
|
+
def inspect
|
106
|
+
'<%s:%s authtype="%s" keytype="%s">' % [self.class.to_s, name, @authtype, @keytype]
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.supported_authentication?(val)
|
110
|
+
["RSA", "DSA"].member?(val || '')
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.supported_keytype?(val)
|
114
|
+
["PRIVATE", "PUBLIC"].member?(val || '')
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
# Creates an OpenSSL::PKey object from +@data+.
|
119
|
+
def parse_data
|
120
|
+
# NOTE: Don't print @data. Not even in debug output. The same goes for +@keypair+.
|
121
|
+
# We don't want private keys to end up somewhere we don't expect them.
|
122
|
+
raise OpenSSL::PKey::PKeyError, "No key data" if @data.nil?
|
123
|
+
@data.strip!
|
124
|
+
@data =~ /\A-----BEGIN (\w+?) (P\w+?) KEY-----$/ # \A matches the string beginning (^ works on lines)
|
125
|
+
raise OpenSSL::PKey::PKeyError, "Bad key data" unless $1 && $2
|
126
|
+
raise OpenSSL::PKey::PKeyError, "Unknown type #{$1}" unless Rye::Key.supported_authentication?($1)
|
127
|
+
raise OpenSSL::PKey::PKeyError, "Unknown value #{$2}" unless Rye::Key.supported_keytype?($2)
|
128
|
+
@authtype, @keytype = $1, $2
|
129
|
+
@keypair = OpenSSL::PKey::RSA.new(@data) if self.rsa?
|
130
|
+
@keypair = OpenSSL::PKey::DSA.new(@data) if self.dsa?
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|