pwss 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.textile +135 -0
- data/Rakefile +1 -0
- data/bin/pwss +222 -0
- data/lib/pwss.rb +110 -0
- data/lib/pwss/cipher.rb +40 -0
- data/lib/pwss/entry.rb +52 -0
- data/lib/pwss/fileops.rb +31 -0
- data/lib/pwss/version.rb +3 -0
- data/pwss.gemspec +32 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a8f78fcbff78980c4edaffd950cdab7ac876c2f1
|
4
|
+
data.tar.gz: c879c1fe675758b9933c74407d22eaec423e2778
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3fb10913eda71dd03f138e5355735657a5b0bfe234932405522474bd5ad94bc4545ac6ce14b6b30e8c78496e6afc83f435d40888ba1d64703fced8d4372f8f24
|
7
|
+
data.tar.gz: dd675b3508cc93830808818e97cb21282120b83412a3b86f9c8f8ae4f51a9a9acb0c799d37765e405072e828d3eda0bdb9bac183cd6ac200d3460dd4777ac58f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Adolfo Villafiorita
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
h1. Pwss
|
2
|
+
|
3
|
+
A password manager in the spirit of "pws":https://github.com/janlelis/pws.
|
4
|
+
|
5
|
+
Features:
|
6
|
+
|
7
|
+
* Multiple entries can be stored in a single file
|
8
|
+
* Entries are "complex" records, including title, username, password, url, description
|
9
|
+
* Support for multiple password files
|
10
|
+
* The password file can be stored encrypted or not
|
11
|
+
* The password file is human-readable and editable (when not encrypted)
|
12
|
+
* Decrypt and encrypt commands allow one to operate directly on the password file
|
13
|
+
|
14
|
+
h2. Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
gem 'pwss'
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install pwss
|
27
|
+
|
28
|
+
h2. Usage
|
29
|
+
|
30
|
+
*Getting started.* @pwss@ stores passwords in YAML files (also called "password safe" in the following), possibly encrypted.
|
31
|
+
|
32
|
+
A typical usage scenario is the following:
|
33
|
+
|
34
|
+
# @pwss init@ will create a new encrypted password safe in @~/.pwss.yaml.enc@
|
35
|
+
# @pwss add@ will add a new entry to the file
|
36
|
+
# @pwss get entry@ will retrieve all entries whose *title* contains @entry@
|
37
|
+
|
38
|
+
*Using multiple safes.* If you want to create your password safe in a different location, use the @-f@ (@--filename@) option:
|
39
|
+
|
40
|
+
# @pwss -f MYFILE init@
|
41
|
+
# @pwss -f MYFILE add@
|
42
|
+
# @pwss -f MYFILE get@
|
43
|
+
|
44
|
+
*Encrypted and Plain Files.* @pwss@ works equally well with encrypted and plain files. More in detailes, the file extension determines whether @pwss@ tries to decrypt/encrypt the file or not. Use @.enc@ to store password safes encrypted; any other extension will leave the file in plain format.
|
45
|
+
|
46
|
+
For instance:
|
47
|
+
|
48
|
+
<pre>
|
49
|
+
$ pwss -f a.yaml.enc init
|
50
|
+
</pre>
|
51
|
+
|
52
|
+
will store the password in to encrypted file @a.yaml.enc@.
|
53
|
+
|
54
|
+
By contrast,
|
55
|
+
|
56
|
+
<pre>
|
57
|
+
$ pwss -f a.yaml get entry
|
58
|
+
</pre>
|
59
|
+
|
60
|
+
will try to retrieve @entry@ from file @a.yaml@, which is not encrypted.
|
61
|
+
|
62
|
+
Encrypting important passwords is a good idea. However, if you use @pwss@ to store non-critical infomation, prefer to edit the password safe with a text editor, or use another application for managing encryption and decryption, using @pwss@ with the file in plain format might be more convenient.
|
63
|
+
|
64
|
+
*Moving from plain to encrypted.* Use the @encrypt@ and @decrypt@ commands at any time to move from the plain to the encrypted format.
|
65
|
+
|
66
|
+
<pre>
|
67
|
+
$ pwss -f YOURFILE encrypt
|
68
|
+
</pre>
|
69
|
+
|
70
|
+
will encrypt @YOURFILE@ while @decrypt@ will perform the opposite operation.
|
71
|
+
|
72
|
+
*Starting from an Existing File.* You can also start from an existing file, as long as it is an array of YAML records, each containing, at least, a @title@ and a @password@ field. (See next section, for the file structure.)
|
73
|
+
|
74
|
+
In this scenario, you can use the following commands to get started:
|
75
|
+
|
76
|
+
# @pwss -f YOURFILE encrypt@ will encrypt your existing password file
|
77
|
+
# @mv YOURFILE.enc ~/.pwss.yaml.enc@ moves the encrypted file to the default location (not necessary, but it simplifies the workflow)
|
78
|
+
|
79
|
+
To add entries to the password safe, use the @add@ command. If you prefer to operate on the file using a text editor, you can also use the @decrypt@ and @encrypt@ commands.
|
80
|
+
|
81
|
+
*Getting Help.*
|
82
|
+
|
83
|
+
<pre>
|
84
|
+
$ pwss
|
85
|
+
</pre>
|
86
|
+
|
87
|
+
will show all command options.
|
88
|
+
|
89
|
+
h2. Under the Hood
|
90
|
+
|
91
|
+
@pwss@ adopts a human-readable format for storing passwords, when the file is not encrypted, of course! (Unless you have mathematical super-powers and can read encrypted text.)
|
92
|
+
|
93
|
+
The password files is a YAML file containing an array of entries. By default, entries have the following records:
|
94
|
+
|
95
|
+
* title
|
96
|
+
* username
|
97
|
+
* password
|
98
|
+
* url
|
99
|
+
* description
|
100
|
+
|
101
|
+
Example
|
102
|
+
|
103
|
+
<pre>
|
104
|
+
- title: A webservice
|
105
|
+
username: username@example.com
|
106
|
+
password: 1234567890
|
107
|
+
url: http://www.example.com
|
108
|
+
description: >
|
109
|
+
with a password like the one above, who needs a password safe
|
110
|
+
|
111
|
+
- title: My email
|
112
|
+
username: username@example.com
|
113
|
+
password: 1234567890
|
114
|
+
url: http://www.example.com
|
115
|
+
description: >
|
116
|
+
Also available via email client, with the following connection parameters
|
117
|
+
smtp.example.com
|
118
|
+
imap.example.com
|
119
|
+
</pre>
|
120
|
+
|
121
|
+
Notice that only @title@ and @password@ are required.
|
122
|
+
|
123
|
+
h2. License and Additional Disclaimer
|
124
|
+
|
125
|
+
Licensed under the terms of the MIT License.
|
126
|
+
|
127
|
+
*Make sure you backup your important data and do not rely solely on @pwss@ to store your critical data.*
|
128
|
+
|
129
|
+
h2. Contributing
|
130
|
+
|
131
|
+
1. Fork it ( http://github.com/<my-github-username>/pwss/fork )
|
132
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
133
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
134
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
135
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/pwss
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'mercenary'
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
require "pwss"
|
8
|
+
require "pwss/version"
|
9
|
+
require "pwss/cipher"
|
10
|
+
require "pwss/entry"
|
11
|
+
require "pwss/fileops"
|
12
|
+
|
13
|
+
Mercenary.program(:pwss) do |p|
|
14
|
+
DEFAULT_FILENAME = File.join(Dir.home, ".pwss.yaml.enc")
|
15
|
+
|
16
|
+
p.version Pwss::VERSION
|
17
|
+
p.description <<EOS
|
18
|
+
PWSS is a password safe, in the spirit of pws
|
19
|
+
|
20
|
+
Features:
|
21
|
+
|
22
|
+
* Each entry stores: title, username, password, url, description
|
23
|
+
* Many entries per file (or 'safe')
|
24
|
+
* Management of different safes
|
25
|
+
* Decrypt and encrypt commands to operate directly on safes
|
26
|
+
* Safes are human-readable and editable (when not encrypted)
|
27
|
+
* Safes can be stored encrypted or not
|
28
|
+
EOS
|
29
|
+
|
30
|
+
p.syntax "pwss [global option] <subcommand> [options] args"
|
31
|
+
p.option 'filename', '--filename FILE', '-f FILE', 'Password file'
|
32
|
+
|
33
|
+
p.command(:init) do |c|
|
34
|
+
c.syntax "init"
|
35
|
+
c.description "Init a new password file"
|
36
|
+
|
37
|
+
c.action do |args, opts|
|
38
|
+
filename = opts['filename'] || DEFAULT_FILENAME
|
39
|
+
|
40
|
+
empty_safe = "# safe created on #{Date.today}\n"
|
41
|
+
|
42
|
+
# check status of input file and encrypt if necessary
|
43
|
+
if FileOps::encrypted? filename then
|
44
|
+
password = Cipher::check_password
|
45
|
+
new_string = Cipher::encrypt empty_safe, password
|
46
|
+
else
|
47
|
+
new_string = empty_safe
|
48
|
+
end
|
49
|
+
|
50
|
+
FileOps::backup(filename) if File.exists?(filename)
|
51
|
+
FileOps::save filename, new_string
|
52
|
+
|
53
|
+
puts "New safe created in #{filename}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
p.command(:list) do |c|
|
58
|
+
c.syntax "list all entries"
|
59
|
+
c.description "List all entries in a safe"
|
60
|
+
|
61
|
+
c.action do |args, opts|
|
62
|
+
filename = opts['filename'] || DEFAULT_FILENAME
|
63
|
+
|
64
|
+
string, _ = file2string filename
|
65
|
+
entries = YAML::load(string) || Array.new
|
66
|
+
|
67
|
+
Pwss::list entries
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
p.command(:get) do |c|
|
72
|
+
c.syntax "get string"
|
73
|
+
c.description "Get first entry matching string in title"
|
74
|
+
c.option 'interactive', '--interactive', '-i', 'Wait for user input to clean password.'
|
75
|
+
|
76
|
+
c.action do |args, opts|
|
77
|
+
filename = opts['filename'] || DEFAULT_FILENAME
|
78
|
+
interactive = opts['interactive']
|
79
|
+
|
80
|
+
string, _ = file2string filename
|
81
|
+
entries = YAML::load(string) || Array.new
|
82
|
+
|
83
|
+
Pwss::get args.join(" "), entries, interactive
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
p.command(:add) do |c|
|
88
|
+
c.syntax "add an entry"
|
89
|
+
c.description "Add an entry to a password safe"
|
90
|
+
|
91
|
+
c.action do |args, opts|
|
92
|
+
filename = opts['filename'] || DEFAULT_FILENAME
|
93
|
+
string, password = file2string filename
|
94
|
+
|
95
|
+
# ask for a new entry
|
96
|
+
pe = Pwss::Entry.new
|
97
|
+
pe.ask
|
98
|
+
|
99
|
+
# add the entry to the safe
|
100
|
+
entries = YAML::load(string) || Array.new
|
101
|
+
entries << pe.entry
|
102
|
+
|
103
|
+
# check status of input file and encrypt if necessary
|
104
|
+
if FileOps::encrypted? filename then
|
105
|
+
new_string = Cipher::encrypt entries.to_yaml, password
|
106
|
+
else
|
107
|
+
new_string = entries.to_yaml
|
108
|
+
end
|
109
|
+
|
110
|
+
FileOps::backup filename
|
111
|
+
FileOps::save filename, new_string
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
p.command(:update) do |c|
|
116
|
+
c.syntax "update the password for an entry"
|
117
|
+
c.description "Update the password for an entry"
|
118
|
+
|
119
|
+
c.action do |args, opts|
|
120
|
+
filename = opts['filename'] || DEFAULT_FILENAME
|
121
|
+
string, password = file2string filename
|
122
|
+
|
123
|
+
# load entries and update
|
124
|
+
entries = YAML::load(string) || Array.new
|
125
|
+
entries = Pwss::update args.join(" "), entries
|
126
|
+
|
127
|
+
# check status of input file and encrypt if necessary
|
128
|
+
if FileOps::encrypted? filename then
|
129
|
+
new_string = Cipher::encrypt entries.to_yaml, password
|
130
|
+
else
|
131
|
+
new_string = entries.to_yaml
|
132
|
+
end
|
133
|
+
|
134
|
+
FileOps::backup filename
|
135
|
+
FileOps::save filename, new_string
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
p.command(:destroy) do |c|
|
141
|
+
c.syntax "delete an entry"
|
142
|
+
c.description "Permenently delete an entry from a safe"
|
143
|
+
|
144
|
+
c.action do |args, opts|
|
145
|
+
filename = opts['filename'] || DEFAULT_FILENAME
|
146
|
+
|
147
|
+
string, password = file2string filename
|
148
|
+
entries = YAML::load(string)
|
149
|
+
|
150
|
+
entries = Pwss::destroy args.join(" "), entries
|
151
|
+
|
152
|
+
# check status of input file and encrypt if necessary
|
153
|
+
if FileOps::encrypted? filename then
|
154
|
+
new_string = Cipher::encrypt entries.to_yaml, password
|
155
|
+
else
|
156
|
+
new_string = entries.to_yaml
|
157
|
+
end
|
158
|
+
|
159
|
+
FileOps::backup filename
|
160
|
+
FileOps::save filename, new_string
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
#
|
166
|
+
# Operations on the master file
|
167
|
+
#
|
168
|
+
|
169
|
+
p.command(:encrypt) do |c|
|
170
|
+
c.syntax "encrypt"
|
171
|
+
c.description "Encrypt a password safe"
|
172
|
+
|
173
|
+
c.action do |_, opts|
|
174
|
+
filename = opts['filename'] || DEFAULT_FILENAME.sub(/\.enc$/, "")
|
175
|
+
|
176
|
+
password = Cipher::check_password
|
177
|
+
data = FileOps::load filename
|
178
|
+
encrypted = Cipher::encrypt data, password
|
179
|
+
|
180
|
+
enc_filename = filename + ".enc"
|
181
|
+
FileOps::save enc_filename, encrypted
|
182
|
+
puts "An encrypted copy now lives in #{enc_filename}"
|
183
|
+
puts "You might want to check everything is ok and delete the plain file: #{filename}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
p.command(:decrypt) do |c|
|
188
|
+
c.syntax "decrypt"
|
189
|
+
c.description "Decrypt a password safe"
|
190
|
+
|
191
|
+
c.action do |_, opts|
|
192
|
+
filename = opts['filename'] || DEFAULT_FILENAME
|
193
|
+
|
194
|
+
password = Cipher::ask_password
|
195
|
+
data = FileOps::load filename
|
196
|
+
decrypted = Cipher::decrypt data, password
|
197
|
+
|
198
|
+
dec_filename = filename.sub(/\.enc$/,"")
|
199
|
+
FileOps::save dec_filename, decrypted
|
200
|
+
puts "A decrypted copy now lives in #{dec_filename}"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
p.command(:help) do |c|
|
205
|
+
c.action do |_,_|
|
206
|
+
puts p.to_s
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
p.default_command(:help)
|
211
|
+
|
212
|
+
# load filename and decrypt, if necessary
|
213
|
+
# return the filename as string and the password (in case you need to save)
|
214
|
+
def file2string filename
|
215
|
+
string = FileOps::load filename
|
216
|
+
if FileOps::encrypted? filename then
|
217
|
+
password = Cipher::ask_password
|
218
|
+
string = Cipher::decrypt string, password
|
219
|
+
end
|
220
|
+
[string, password]
|
221
|
+
end
|
222
|
+
end
|
data/lib/pwss.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
#
|
4
|
+
# This module reasons at the entries level
|
5
|
+
# (list of entry)
|
6
|
+
#
|
7
|
+
|
8
|
+
module Pwss
|
9
|
+
|
10
|
+
WAITING_PERIOD=30 # seconds
|
11
|
+
|
12
|
+
def self.get search_string, entries, interactive
|
13
|
+
id = choose_entry search_string, entries
|
14
|
+
|
15
|
+
# it causes confusion ... here id is the absolute id
|
16
|
+
# (the one printed by choose_entry is the relative match)
|
17
|
+
# puts "Selected entry:\n"
|
18
|
+
# print_entry id, entries[id]
|
19
|
+
|
20
|
+
#
|
21
|
+
# make the password available and then forget it
|
22
|
+
#
|
23
|
+
password = entries[id]["password"]
|
24
|
+
system("echo #{password} | pbcopy")
|
25
|
+
|
26
|
+
if interactive
|
27
|
+
puts "\nPassword available in clipboard: press any key when you are done."
|
28
|
+
STDIN.getc
|
29
|
+
else
|
30
|
+
puts "\nPassword available in clipboard for #{WAITING_PERIOD} seconds."
|
31
|
+
sleep(WAITING_PERIOD)
|
32
|
+
end
|
33
|
+
|
34
|
+
system("echo ahahahahaha | pbcopy")
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def self.update search_string, entries
|
39
|
+
id = choose_entry search_string, entries
|
40
|
+
password = Cipher::check_password
|
41
|
+
entries[id]["password"] = password
|
42
|
+
entries
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def self.destroy search_string, entries
|
47
|
+
id = choose_entry search_string, entries
|
48
|
+
entries.delete_at(id) if id != -1
|
49
|
+
entries
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def self.list entries
|
54
|
+
index = 0
|
55
|
+
entries.each do |element|
|
56
|
+
print_entry index, element
|
57
|
+
index += 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
#
|
64
|
+
# Let the user select an entry from data
|
65
|
+
# (data is a YAML string with an array of entries)
|
66
|
+
#
|
67
|
+
def self.choose_entry search_string, entries
|
68
|
+
# here we have a nuisance: we want the user to choose one entry
|
69
|
+
# by relative id (e.g. the third found), but we need to return
|
70
|
+
# the absolute id... so we just keep track of the real ids with an array
|
71
|
+
# we ask the user the index of the array
|
72
|
+
|
73
|
+
index = 0
|
74
|
+
found = Array.new
|
75
|
+
entries.each do |entry|
|
76
|
+
if entry["title"].downcase.include?(search_string.downcase)
|
77
|
+
print_entry found.size, entry
|
78
|
+
found << index
|
79
|
+
end
|
80
|
+
index += 1
|
81
|
+
end
|
82
|
+
|
83
|
+
if found.size == 0 then
|
84
|
+
printf "\nNo entry matches the search criteria."
|
85
|
+
return -1
|
86
|
+
end
|
87
|
+
|
88
|
+
if found.size > 1 then
|
89
|
+
printf "\nVarious matches. Select entry by ID (0..#{found.size-1}): "
|
90
|
+
id = STDIN.gets.chomp.to_i
|
91
|
+
else
|
92
|
+
id = 0
|
93
|
+
end
|
94
|
+
|
95
|
+
found[id]
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Print entry
|
100
|
+
#
|
101
|
+
def self.print_entry id, element
|
102
|
+
puts "\n---\nENTRY ID: #{id}"
|
103
|
+
# we need to duplicate, because deletion in place will remove
|
104
|
+
# passwords from entries (and, frankly, we need them)
|
105
|
+
new_el = element.dup
|
106
|
+
new_el.delete("password")
|
107
|
+
puts new_el.to_yaml
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
data/lib/pwss/cipher.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'encryptor'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Cipher does encryption and decryption of data
|
5
|
+
# It reasons at the string level (both in input and in output)
|
6
|
+
#
|
7
|
+
module Cipher
|
8
|
+
def self.encrypt string, password
|
9
|
+
Encryptor.encrypt(:value => string, :key => password)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.decrypt string, password
|
13
|
+
Encryptor.decrypt(:value => string, :key => password)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Ask for a password fom the command line
|
17
|
+
def self.ask_password prompt="Enter master password: "
|
18
|
+
printf prompt
|
19
|
+
system "stty -echo"
|
20
|
+
password = $stdin.gets.chomp
|
21
|
+
system "stty echo"
|
22
|
+
puts ""
|
23
|
+
password
|
24
|
+
end
|
25
|
+
|
26
|
+
# Ask for a password twice and make sure it is entered the same
|
27
|
+
def self.check_password prompt="master password: "
|
28
|
+
match = false
|
29
|
+
while ! match
|
30
|
+
password = ask_password "Enter #{prompt}"
|
31
|
+
repeat = ask_password "Repeat #{prompt}"
|
32
|
+
match = (password == repeat)
|
33
|
+
|
34
|
+
if match == false then
|
35
|
+
puts "Error! Password do not match. Please enter them again."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
password
|
39
|
+
end
|
40
|
+
end
|
data/lib/pwss/entry.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Pwss
|
2
|
+
#
|
3
|
+
# Entry manages an entry in the password safe
|
4
|
+
# It is a wrapper to a Hash
|
5
|
+
#
|
6
|
+
class Entry
|
7
|
+
INPUT_F=0
|
8
|
+
DEFAULT=1
|
9
|
+
PROMPT=2
|
10
|
+
|
11
|
+
# the fields of an entry, together with the function to ask the, default value, and prompt
|
12
|
+
FIELDS = {
|
13
|
+
"title" => ["gets.chomp", "'title'", "title: "],
|
14
|
+
"username" => ["gets.chomp", "''", "username: "],
|
15
|
+
"password" => ["Cipher.check_password('password for entry: ')", "''", ""],
|
16
|
+
"added" => ["", "Date.today.to_s", ""],
|
17
|
+
"url" => ["gets.chomp", "''", "url: "],
|
18
|
+
"description" => ["get_lines", "''", "description (terminate with '.'):\n"]
|
19
|
+
}
|
20
|
+
|
21
|
+
# the values (a Hash) of this issue
|
22
|
+
attr_reader :entry
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@entry = Hash.new
|
26
|
+
end
|
27
|
+
|
28
|
+
# interactively ask from command line all fields specified in FIELDS
|
29
|
+
def ask
|
30
|
+
FIELDS.keys.each do |key|
|
31
|
+
printf "#{FIELDS[key][PROMPT]}" if FIELDS[key][PROMPT] != ""
|
32
|
+
@entry[key] = (eval FIELDS[key][INPUT_F]) || (eval FIELDS[key][DEFAULT])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# initialize all fields with the default values
|
37
|
+
# (and set title to the argument)
|
38
|
+
# def set_fields title
|
39
|
+
# FIELDS.keys.each do |k|
|
40
|
+
# @entry[k] = eval(FIELDS[k][DEFAULT])
|
41
|
+
# end
|
42
|
+
# @entry['title'] = title
|
43
|
+
# end
|
44
|
+
|
45
|
+
# read n-lines (terminated by a ".")
|
46
|
+
def get_lines
|
47
|
+
$/ = "\n.\n"
|
48
|
+
STDIN.gets.chomp("\n.\n")
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
data/lib/pwss/fileops.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
#
|
4
|
+
# From file to string and back
|
5
|
+
# There is no lower level than this
|
6
|
+
#
|
7
|
+
module FileOps
|
8
|
+
# load a file into a string
|
9
|
+
def self.load filename
|
10
|
+
file = File.open(filename, "rb")
|
11
|
+
file.read
|
12
|
+
end
|
13
|
+
|
14
|
+
# save a string to a file
|
15
|
+
def self.save filename, data
|
16
|
+
file = File.open(filename, "wb")
|
17
|
+
file.write data
|
18
|
+
file.close
|
19
|
+
end
|
20
|
+
|
21
|
+
# check if the extension is ".enc"
|
22
|
+
def self.encrypted? filename
|
23
|
+
File.extname(filename) == ".enc"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.backup filename
|
27
|
+
FileUtils::cp filename, filename + "~"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
data/lib/pwss/version.rb
ADDED
data/pwss.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pwss/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "pwss"
|
8
|
+
spec.version = Pwss::VERSION
|
9
|
+
spec.authors = ["Adolfo Villafiorita"]
|
10
|
+
spec.email = ["adolfo.villafiorita@me.com"]
|
11
|
+
spec.summary = %q{A password manager in the spirit of pwss}
|
12
|
+
spec.description = %q{PWSS is a password safe, in the spirit of pws
|
13
|
+
Distinguishing features:
|
14
|
+
- all entries are stored in a single file
|
15
|
+
- entries are "complex" records, with username, password, url, description
|
16
|
+
- the safe file can be stored encrypted or not
|
17
|
+
- decrypt and encrypt command allow to operate directly on the password file
|
18
|
+
}
|
19
|
+
spec.homepage = "http://www.github.com/avillafiorita/pwss"
|
20
|
+
spec.license = "MIT"
|
21
|
+
|
22
|
+
spec.files = `git ls-files -z`.split("\x0")
|
23
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
24
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
28
|
+
spec.add_development_dependency "rake"
|
29
|
+
|
30
|
+
spec.add_runtime_dependency 'mercenary', '~> 0.3.3', '>= 0.3.3'
|
31
|
+
spec.add_runtime_dependency 'encryptor', '~> 1.3.0', '>= 1.3.0'
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pwss
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adolfo Villafiorita
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mercenary
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.3.3
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 0.3.3
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 0.3.3
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 0.3.3
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: encryptor
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.3.0
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 1.3.0
|
71
|
+
type: :runtime
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.3.0
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 1.3.0
|
81
|
+
description: |
|
82
|
+
PWSS is a password safe, in the spirit of pws
|
83
|
+
Distinguishing features:
|
84
|
+
- all entries are stored in a single file
|
85
|
+
- entries are "complex" records, with username, password, url, description
|
86
|
+
- the safe file can be stored encrypted or not
|
87
|
+
- decrypt and encrypt command allow to operate directly on the password file
|
88
|
+
email:
|
89
|
+
- adolfo.villafiorita@me.com
|
90
|
+
executables:
|
91
|
+
- pwss
|
92
|
+
extensions: []
|
93
|
+
extra_rdoc_files: []
|
94
|
+
files:
|
95
|
+
- ".gitignore"
|
96
|
+
- Gemfile
|
97
|
+
- LICENSE.txt
|
98
|
+
- README.textile
|
99
|
+
- Rakefile
|
100
|
+
- bin/pwss
|
101
|
+
- lib/pwss.rb
|
102
|
+
- lib/pwss/cipher.rb
|
103
|
+
- lib/pwss/entry.rb
|
104
|
+
- lib/pwss/fileops.rb
|
105
|
+
- lib/pwss/version.rb
|
106
|
+
- pwss.gemspec
|
107
|
+
homepage: http://www.github.com/avillafiorita/pwss
|
108
|
+
licenses:
|
109
|
+
- MIT
|
110
|
+
metadata: {}
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 2.2.2
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: A password manager in the spirit of pwss
|
131
|
+
test_files: []
|