simplepass 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2008-07-28
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
@@ -0,0 +1,8 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/simplepass
6
+ lib/simplepass.rb
7
+ spec/simplepass_spec.rb
8
+ spec/data
@@ -0,0 +1,107 @@
1
+ = simplepass
2
+
3
+ * Project homepage: http://danielchoi.com/software/simplepass.html
4
+
5
+ == DESCRIPTION:
6
+
7
+ simplepass is a lightweight, secure password database with a simple command-line
8
+ interface.
9
+
10
+ == FEATURES/PROBLEMS:
11
+
12
+ * Uses the Blowfish encryption algorithm to securely store your passwords.
13
+ * Can export all your passwords into an unencrypted YAML format whenever you
14
+ need to.
15
+
16
+ == SYNOPSIS:
17
+
18
+ Usage: simplepass [options] [domain name]
19
+
20
+ Where domain name is typically a website domain, e.g. yahoo.com. The domain can
21
+ also be any other string, e.g. "Gmail account".
22
+
23
+ For example, you enter
24
+
25
+ simplepass gmail.com
26
+
27
+ If your domain name has spaces, remember to quote it like so:
28
+
29
+ simplepass 'Gmail account'
30
+
31
+ for the first time, simplepass will launch your text editor (whatever your
32
+ EDITOR environment variable has been set to) and present you with a simple form
33
+ that you can fill out to save a login, password, and arbitrary notes for that
34
+ domain:
35
+
36
+ gmail.com
37
+ login:
38
+ password:
39
+ notes:
40
+
41
+ You can leave any of the fields blank. So if you want, you can just fill out
42
+ the 'notes' portion for items that are not web logins, such as credit card
43
+ numbers and such.
44
+
45
+ Once you fill out the fields save the file and exit your editor, simplepass will
46
+ parse the information, encrypt it, store it in the database, and delete the
47
+ temporary file.
48
+
49
+ The next time you give the command,
50
+
51
+ simplepass gmail.com
52
+
53
+ simplepass will display something like this, via the 'less' command: (This is so
54
+
55
+ gmail.com
56
+ login: funnyface
57
+ password: audreyhepburn
58
+ notes: I love gmail! blah... blah...
59
+
60
+ (We pipe the output to 'less' so as to leave no trace of your password in your
61
+ console, where someone can find it by scrolling up.)
62
+
63
+ The first time you launch simplepass, it will ask you to set a master password.
64
+ This password will unlock the simplepass database. This database is stored in
65
+ a single file: a partly encrypted text file called simplepass.db. This file will be
66
+ saved in the directory in which you invoked the #{opt.program_name} command.
67
+
68
+ The top part of this file is a message in plain text and just serves to remind
69
+ you of the file's purpose. The bottom part is your password database, which is
70
+ nothing more than a YAML string encrypted with the Blowfish encryption
71
+ algorithm. Your master password is the the key that decrypts this portion of the
72
+ simplepass.db file. Do not edit this file directly; any edits may render the
73
+ data un-decryptable.
74
+
75
+
76
+ == REQUIREMENTS:
77
+
78
+ * crypt
79
+
80
+ == INSTALL:
81
+
82
+ * sudo gem install simplepass
83
+
84
+ == LICENSE:
85
+
86
+ (The MIT License)
87
+
88
+ Copyright (c) 2008 FIX
89
+
90
+ Permission is hereby granted, free of charge, to any person obtaining
91
+ a copy of this software and associated documentation files (the
92
+ 'Software'), to deal in the Software without restriction, including
93
+ without limitation the rights to use, copy, modify, merge, publish,
94
+ distribute, sublicense, and/or sell copies of the Software, and to
95
+ permit persons to whom the Software is furnished to do so, subject to
96
+ the following conditions:
97
+
98
+ The above copyright notice and this permission notice shall be
99
+ included in all copies or substantial portions of the Software.
100
+
101
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
102
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
103
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
104
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
105
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
106
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
107
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/simplepass.rb'
6
+
7
+ Hoe.new('Simple Password Manager', SimplePass::VERSION) do |p|
8
+ p.name = 'simplepass'
9
+ p.rubyforge_name = 'simplepass' # if different than lowercase project name
10
+ p.developer('Daniel Choi', 'dhchoi@gmail.com')
11
+ p.extra_deps << ['crypt', '>=1.1.4']
12
+ p.remote_rdoc_dir = 'rdoc'
13
+ p.post_install_message = 'Type simplepass -h for instructions.'
14
+ end
15
+
16
+ # vim: syntax=Ruby
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'simplepass'
3
+
4
+ SimplePass.run ARGV
@@ -0,0 +1,392 @@
1
+ require 'rubygems'
2
+ require 'tempfile'
3
+ require 'optparse'
4
+ require 'crypt/blowfish'
5
+ require 'delegate'
6
+ require 'yaml'
7
+ $:.unshift File.dirname(__FILE__)
8
+
9
+ class SimplePass < DelegateClass(Hash)
10
+ VERSION = "1.0.0"
11
+
12
+ def self.process_args(argv)
13
+ @options = options = {
14
+ :editor => ENV['EDITOR'] || 'nano'
15
+ }
16
+ opts = OptionParser.new do |opt|
17
+ opt.program_name = File.basename $0
18
+ opt.version = SimplePass::VERSION
19
+ opt.summary_indent = ' ' * 4
20
+ opt.banner = <<-EOT
21
+ Usage: #{opt.program_name} [options] [domain name]
22
+
23
+ Where domain name is typically a website domain, e.g. yahoo.com. The domain can
24
+ also be any other string, e.g. "Gmail account".
25
+
26
+ For example, you enter
27
+
28
+ #{opt.program_name} gmail.com
29
+
30
+ If your domain name has spaces, remember to quote it like so:
31
+
32
+ #{opt.program_name} 'Gmail account'
33
+
34
+ for the first time, simplepass will launch your text editor (whatever your
35
+ EDITOR environment variable has been set to) and present you with a simple form
36
+ that you can fill out to save a login, password, and arbitrary notes for that
37
+ domain:
38
+
39
+ gmail.com
40
+ login:
41
+ password:
42
+ notes:
43
+
44
+ You can leave any of the fields blank. So if you want, you can just fill out
45
+ the 'notes' portion for items that are not web logins, such as credit card
46
+ numbers and such.
47
+
48
+ Once you fill out the fields save the file and exit your editor, simplepass will
49
+ parse the information, encrypt it, store it in the database, and delete the
50
+ temporary file.
51
+
52
+ The next time you give the command,
53
+
54
+ #{opt.program_name} gmail.com
55
+
56
+ simplepass will display something like this, via the 'less' command: (This is so
57
+
58
+ gmail.com
59
+ login: funnyface
60
+ password: audreyhepburn
61
+ notes: I love gmail! blah... blah...
62
+
63
+ (We pipe the output to 'less' so as to leave no trace of your password in your
64
+ console, where someone can find it by scrolling up.)
65
+
66
+ The first time you launch simplepass, it will ask you to set a master password.
67
+ This password will unlock the simplepass database. This database is stored in
68
+ a single file: a partly encrypted text file called simplepass.db. This file will be
69
+ saved in the directory in which you invoked the #{opt.program_name} command.
70
+
71
+ The top part of this file is a message in plain text and just serves to remind
72
+ you of the file's purpose. The bottom part is your password database, which is
73
+ nothing more than a YAML string encrypted with the Blowfish encryption
74
+ algorithm. Your master password is the the key that decrypts this portion of the
75
+ simplepass.db file. Do not edit this file directly; any edits may render the
76
+ data un-decryptable.
77
+ EOT
78
+ opt.separator nil
79
+ opt.separator "Options:"
80
+ opt.separator nil
81
+
82
+ opt.on("--edit", "-e",
83
+ "Edit the login information for the domain.",
84
+ "This invokes your text editor.") do |value|
85
+ options[:edit] = true
86
+ end
87
+ opt.separator nil
88
+ opt.on("--remove", "-r",
89
+ "Deletes the entry for the domain.") do |value|
90
+ options[:remove] = true
91
+ end
92
+ opt.on("--list", "-l",
93
+ "List all the domains in the databse.") do |value|
94
+ options[:list] = true
95
+ end
96
+ opt.on("--change-password", "-p",
97
+ "Changes the master password.") do |value|
98
+ options[:change_password] = true
99
+ end
100
+ opt.on("--dump", "-d",
101
+ "If you specify a domain, this causes the ",
102
+ "login info to be printed out directly in ",
103
+ "the console, instead of via 'less'. If you",
104
+ "don't specify a domain, this decrypts and ",
105
+ "dumps the entire password database into a ",
106
+ "YAML string. This string is printed to ",
107
+ "STDOUT, so you can redirect it into a file.",
108
+ "This is to ensure that you can get all your",
109
+ "login data out whenever you want.") do |value|
110
+ options[:dump] = true
111
+ end
112
+ end
113
+
114
+ opts.parse! argv
115
+ unless argv.first || options[:dump] || options[:list] || options[:change_password]
116
+ raise OptionParser::InvalidArgument, "You must specify a domain name."
117
+ end
118
+ options
119
+ rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
120
+ puts opts
121
+ puts
122
+ puts e
123
+ exit 1
124
+ end
125
+
126
+ def self.run(argv = ARGV)
127
+ options = process_args argv
128
+ domain = argv.shift
129
+ @db = db = if database_exists?
130
+ unlock_database
131
+ else
132
+ create_database
133
+ end
134
+ if options[:change_password]
135
+ puts "To change the master password:"
136
+ password = set_password
137
+ @db.change_master_password(password)
138
+ @db.save!
139
+ puts "Master password changed."
140
+ elsif options[:list]
141
+ puts db.ui.list.join("\n")
142
+ elsif options[:remove]
143
+ puts db.ui.remove(domain)
144
+ elsif options[:dump]
145
+ puts db.ui.dump(domain)
146
+ else
147
+ # lookup the domain
148
+ output = db.ui.decrypt(domain)
149
+ if output
150
+ if options[:edit]
151
+ output = UI::EDIT_HEADER + output
152
+ edit_entry( output )
153
+ else
154
+ IO.popen("less", "w") do |pipe|
155
+ pipe.puts output
156
+ end
157
+ end
158
+ else
159
+ # if no entry, create one
160
+ # We loop so user can add multiple entries without having to enter the
161
+ # password every time.
162
+ loop do
163
+ puts "Creating entry."
164
+ sleep 1
165
+ edit_entry( UI::EDIT_HEADER + db.ui.new_entry(domain) )
166
+ puts "Added entry for #{domain}"
167
+ print "Add another? (y/n) "
168
+ reply = STDIN.gets.chomp
169
+ unless reply =~ /y/i
170
+ puts "Ok, good bye."
171
+ exit
172
+ end
173
+ puts
174
+ print "Domain name for new entry: "
175
+ domain = STDIN.gets.chomp
176
+ end
177
+ end
178
+ end
179
+
180
+ exit
181
+ # create database if no database in current dir
182
+ # ask for master password
183
+ # ask for password confirm
184
+
185
+ if options[:dump]
186
+ # handle dump
187
+ end
188
+ domain = ARGV.shift
189
+ match = decrypt(domain)
190
+ if match
191
+ # show in less
192
+ else
193
+ # create entry
194
+ end
195
+ end
196
+
197
+ def self.edit_entry(entry)
198
+ tf = Tempfile.new('simplepass')
199
+ tf.puts( entry )
200
+ tf.close
201
+ system("#{@options[:editor]} #{tf.path}")
202
+ tf.open
203
+ entry = tf.read
204
+ @db.ui.add(entry)
205
+ @db.save!
206
+ tf.close(true)
207
+ end
208
+
209
+ def self.database_exists?
210
+ File.exist?('simplepass.db')
211
+ end
212
+
213
+ def self.unlock_database
214
+ loop do
215
+ print "Master password: "
216
+ `stty -echo`
217
+ password = STDIN.gets.chomp
218
+ `stty echo`
219
+ puts
220
+ exit if password == ''
221
+ db = SimplePass.new(password, 'simplepass.db')
222
+ begin
223
+ db.load
224
+ break db
225
+ rescue Exception
226
+ puts "Sorry, that password isn't correct."
227
+ end
228
+ end
229
+ end
230
+
231
+ def self.create_database
232
+ print "Can't find a simplepass.db database in this directory. Create one? (y/n) "
233
+ reply = STDIN.gets.chomp
234
+ puts
235
+
236
+ unless reply =~ /y/i
237
+ puts "Ok, good bye."
238
+ exit
239
+ end
240
+ password = set_password
241
+ puts "Creating database simplepass.db and encrypting it with the master password."
242
+ db = SimplePass.new(password, 'simplepass.db')
243
+ db.save!
244
+ db
245
+ end
246
+
247
+ def self.set_password
248
+ password = nil
249
+ loop do
250
+ print "Please type a master password (will not show what you type): "
251
+ `stty -echo`
252
+ password = STDIN.gets.chomp
253
+ puts
254
+ print "Please confirm by typing it again: "
255
+ password_confirm = STDIN.gets.chomp
256
+ puts
257
+ unless password == password_confirm
258
+ puts "Sorry, you passwords do not match."
259
+ else
260
+ `stty echo`
261
+ break
262
+ end
263
+ end
264
+ password
265
+ end
266
+
267
+ HEADER = "This is an encrypted SimplePass password file. Do not edit this file directly.\n"
268
+ DIVIDER = "====BEGIN DATA===\n"
269
+ attr_accessor :master_password, :header
270
+ def initialize(master_password, file=nil)
271
+ @master_password = master_password
272
+ @blowfish = Crypt::Blowfish.new(@master_password)
273
+ @file = file
274
+ @header = HEADER
275
+ super({})
276
+ end
277
+
278
+ def change_master_password(password)
279
+ @master_password = password
280
+ @blowfish = Crypt::Blowfish.new(@master_password)
281
+ end
282
+
283
+ def encrypt(plain_string)
284
+ @blowfish.encrypt_string(plain_string)
285
+ end
286
+
287
+ def decrypt(encrypted_string)
288
+ @blowfish.decrypt_string(encrypted_string)
289
+ end
290
+
291
+ def load
292
+ File.open(@file, "r") do |file|
293
+ content = file.read
294
+ # strip the header
295
+ @header, data = content.split(DIVIDER, 2)
296
+ hash = YAML::load(decrypt(data))
297
+ hash.each_pair {|k,v| self[k] = v}
298
+ end
299
+ end
300
+
301
+ def self.load(master_password, file)
302
+ simplepass = self.new(master_password, file)
303
+ simplepass.load
304
+ simplepass
305
+ end
306
+
307
+ def save!
308
+ File.open(@file, "w") do |file|
309
+ # Write an informative header
310
+ file.write(@header + DIVIDER)
311
+ # Dump the encrypted data
312
+ dump = YAML::dump(self)
313
+ file.write(encrypt(dump))
314
+ end
315
+ end
316
+ alias_method :save, :save!
317
+
318
+ def ui
319
+ @ui ||= UI.new(self)
320
+ @ui
321
+ end
322
+
323
+ class UI
324
+ EDIT_HEADER =
325
+ "Fill the the fields below. Leave the first line, with the name of the domain,
326
+ untouched. The notes field can accept multiple lines, including blank lines.\n" + ('-' * 20 + "\n")
327
+
328
+ def initialize(simplepass)
329
+ @database = simplepass
330
+ end
331
+
332
+ def decrypt(domain)
333
+ matching_key = @database.keys.detect {|k| k == domain}
334
+ return nil unless matching_key
335
+ # format the YAML to remove weird symbol markup
336
+ result = @database[matching_key]
337
+ formatted_result = "#{matching_key}\nlogin: #{result[:login]}\npassword: #{result[:password]}\nnotes: #{result[:notes]}\n"
338
+ end
339
+
340
+ def list
341
+ @database.keys.sort
342
+ end
343
+
344
+ def strip_header(text)
345
+ text.split(('-' * 20) + "\n", 2)[-1]
346
+ end
347
+
348
+ def add(text)
349
+ entry = {}
350
+ # strip off header text, which is just for instructions
351
+ text = strip_header(text)
352
+ # split text into lines
353
+ # first line is the domain
354
+ domain = text.split("\n")[0].chomp
355
+ login = text.match(/^login:(.*)/)[1].strip
356
+ password = text.match(/^password:(.*)/)[1].strip
357
+ notes = text.split(/^notes:\s*/, 2)[1].strip
358
+ @database[domain] = {:login => login, :password => password, :notes => notes}
359
+ end
360
+
361
+ def dump(domain=nil)
362
+ unless domain
363
+ return @database.to_yaml
364
+ end
365
+ @database[domain].to_yaml
366
+ end
367
+
368
+ def new_entry(domain)
369
+ return <<END
370
+ #{domain}
371
+ login:
372
+ password:
373
+ notes:
374
+ END
375
+ end
376
+
377
+ def remove(domain)
378
+ unless decrypt(domain)
379
+ return "There is no entry for #{domain} in the database."
380
+ end
381
+ @database.delete(domain)
382
+ @database.save!
383
+ puts = "#{domain} deleted."
384
+ end
385
+
386
+ end
387
+
388
+ end
389
+
390
+ if __FILE__ == $0
391
+ SimplePass.run ARGV
392
+ end
@@ -0,0 +1,121 @@
1
+ require File.dirname(__FILE__) + "/../lib/simplepass.rb"
2
+ require 'fileutils'
3
+
4
+ data_dir = File.dirname(__FILE__) + '/data'
5
+
6
+ describe "SimplePass: Core Engine" do
7
+
8
+ before do
9
+ @master_password = "betahouse"
10
+ @test_file = data_dir + "/testSimplePass.txt"
11
+ @simplepass = SimplePass.new(@master_password, @test_file)
12
+ end
13
+
14
+ it "should encrypt and decrypt a plain text string" do
15
+ # These are lower level methods
16
+ encrypted = @simplepass.encrypt("hello world")
17
+ decrypted = @simplepass.decrypt(encrypted)
18
+ decrypted.should == "hello world"
19
+ end
20
+
21
+ it "should act like a hash of entries, keyed by domain name" do
22
+ # Use these higher level methods
23
+ @simplepass['yahoo.com'] = {:login => "pasta", :password => "pizza"}
24
+ @simplepass['yahoo.com'][:password].should == 'pizza'
25
+ @simplepass['yahoo.com'][:login].should == 'pasta'
26
+ @simplepass['google.com'].should be_nil
27
+ end
28
+
29
+ it "should be able to retrieve a password by key (domain)" do
30
+ @simplepass['yahoo.com'] = {:login => "pasta", :password => "pizza"}
31
+ @simplepass['yahoo.com'].should == {:login => "pasta", :password => "pizza"}
32
+ end
33
+
34
+ it "should save the passwords into an encypted YAML file" do
35
+ @simplepass['yahoo.com'] = {:login => "pasta", :password => "pizza"}
36
+ @simplepass['flickr.com'] = {:login => "apple", :password => "orange", :note => "hello!"}
37
+
38
+ @simplepass.save!
39
+
40
+ new_simplepass = SimplePass.load("betahouse", @test_file)
41
+ new_simplepass['yahoo.com'][:login].should == 'pasta'
42
+ new_simplepass['flickr.com'][:note].should == "hello!"
43
+ new_simplepass['flickr.com'][:password].should == "orange"
44
+ end
45
+
46
+ it "should put a default unecrypted header section on the file" do
47
+ @simplepass.header.should == SimplePass::HEADER
48
+ end
49
+
50
+ it "should fail to decrypt a file with the wrong password" do
51
+ @simplepass['yahoo.com'] = {:login => "pasta", :password => "pizza"}
52
+ @simplepass.save!
53
+ lambda do
54
+ @simplepass2 = SimplePass.new("wrong password", @test_file)
55
+ @simplepass2.load
56
+ end.should raise_error(Exception)
57
+ @simplepass3 = SimplePass.new("betahouse", @test_file).load
58
+ @simplepass3['yahoo.com'].should == {:login => "pasta", :password => "pizza"}
59
+ end
60
+
61
+ after(:all) do
62
+ FileUtils.rm(@test_file)
63
+ end
64
+ end
65
+
66
+ # Use less as the viewer because it does not leave traces of the displayed
67
+ # content in the bash shell history.
68
+ describe "User Interface: a user" do
69
+ before do
70
+ @test_file = data_dir + "/testSimplePass.txt"
71
+ @master_password = "betahouse"
72
+ @simplepass = SimplePass.new(@master_password, @test_file)
73
+ @simplepass['yahoo.com'] = {:login => "pasta", :password => "pizza"}
74
+ @simplepass['google.com'] = {:login => "glue", :password => "cheese"}
75
+ @simplepass['goo.com'] = {:login => "dog", :password => "rock"}
76
+ @simplepass.keys.size.should == 3
77
+ @ui = @simplepass.ui
78
+ end
79
+
80
+ it "should be able to retrieve a password as a YAML hash" do
81
+ @ui.decrypt('yahoo.com').should == "yahoo.com\nlogin: pasta\npassword: pizza\nnotes: \n"
82
+ end
83
+
84
+ it "should retrieve an empty if no matches" do
85
+ @ui.decrypt('blah').should be_nil
86
+ end
87
+
88
+ it "should read in a YAML and add it to the password database" do
89
+ entry = <<-yaml
90
+ header text - will be stripped
91
+ --------------------
92
+ dogster.com
93
+ login: u2
94
+ password: hello
95
+ notes: my dog
96
+ is the best
97
+ yaml
98
+ @ui.add(entry)
99
+ @simplepass['dogster.com'].should == {:login => 'u2', :password => 'hello', :notes => "my dog\nis the best"}
100
+ @ui.decrypt('dogster.com').should == "dogster.com\nlogin: u2\npassword: hello\nnotes: my dog\nis the best\n"
101
+ @simplepass.keys.size.should == 4
102
+ end
103
+
104
+ it "should decrypt the entire password database" do
105
+ @ui.dump.should == @simplepass.to_yaml
106
+ end
107
+
108
+ it "should prepare a form for a new entry" do
109
+ @ui.new_entry('amazon.com').should == "amazon.com\nlogin: \npassword: \nnotes: \n"
110
+ end
111
+
112
+ it "should delete a domain" do
113
+ @ui.remove("goo.com").should == "goo.com deleted."
114
+ @simplepass['goo.com'].should be_nil
115
+ end
116
+
117
+ after(:all) do
118
+ FileUtils.rm(@test_file)
119
+ end
120
+ end
121
+
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simplepass
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Choi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-28 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: crypt
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.1.4
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.7.0
34
+ version:
35
+ description: simplepass is a lightweight, secure password database with a simple command-line interface.
36
+ email:
37
+ - dhchoi@gmail.com
38
+ executables:
39
+ - simplepass
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - History.txt
44
+ - Manifest.txt
45
+ - README.txt
46
+ files:
47
+ - History.txt
48
+ - Manifest.txt
49
+ - README.txt
50
+ - Rakefile
51
+ - bin/simplepass
52
+ - lib/simplepass.rb
53
+ - spec/simplepass_spec.rb
54
+ - spec/data
55
+ has_rdoc: true
56
+ homepage: "Project homepage: http://danielchoi.com/software/simplepass.html"
57
+ post_install_message: Type simplepass -h for instructions.
58
+ rdoc_options:
59
+ - --main
60
+ - README.txt
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ requirements: []
76
+
77
+ rubyforge_project: simplepass
78
+ rubygems_version: 1.2.0
79
+ signing_key:
80
+ specification_version: 2
81
+ summary: simplepass is a lightweight, secure password database with a simple command-line interface.
82
+ test_files: []
83
+