simplepass 1.0.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.
@@ -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
+