pwss 0.0.2
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.
- 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: []
|