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.
- data/History.txt +6 -0
- data/Manifest.txt +8 -0
- data/README.txt +107 -0
- data/Rakefile +16 -0
- data/bin/simplepass +4 -0
- data/lib/simplepass.rb +392 -0
- data/spec/simplepass_spec.rb +121 -0
- metadata +83 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/bin/simplepass
ADDED
data/lib/simplepass.rb
ADDED
@@ -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
|
+
|