gtk2passwordapp 0.0.8 → 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/README.txt +46 -3
- data/bin/gtk2passwordapp +25 -36
- data/gtk2passwordapp/appconfig.rb +29 -0
- data/gtk2passwordapp/edit_box.rb +328 -0
- data/gtk2passwordapp/iocrypt.rb +30 -0
- data/gtk2passwordapp/passwords.rb +93 -0
- data/{lib → gtk2passwordapp}/passwords_data.rb +5 -14
- data/pngs/logo.png +0 -0
- metadata +25 -18
- data/bin/gtk2passwordmenu +0 -43
- data/gifs/logo.gif +0 -0
- data/lib/configuration.rb +0 -54
- data/lib/global_options_variables.rb +0 -40
- data/lib/gtk2passwordapp.rb +0 -623
- data/lib/gtk2passwordmenu.rb +0 -145
- data/lib/iocrypt.rb +0 -40
- data/lib/setup_user_space.rb +0 -37
data/README.txt
CHANGED
@@ -1,7 +1,50 @@
|
|
1
|
-
|
2
|
-
gtk2passwordapp
|
1
|
+
Ruby-Gnome Password Manager
|
3
2
|
|
4
|
-
|
3
|
+
A Ruby-Gnome password manager.
|
4
|
+
Uses crypt-tea's Tiny Encryption Algorithm to encrypt the datafile.
|
5
|
+
Features random password generator and clipboard use.
|
6
|
+
|
7
|
+
|
8
|
+
To add an account, enter the new account name in the "Account:" entry/combo box.
|
9
|
+
For the account, write the associated url, a note about the accout, and the username
|
10
|
+
in the appropriate entry boxes.
|
11
|
+
|
12
|
+
To set a new password, either enter the password in the "New:" entry box, or
|
13
|
+
generate it by pressing "Random", "Alpha-Numeric", "Numeric", "Letters", or
|
14
|
+
"All-Caps". One can set the password length generated with the spin-box.
|
15
|
+
To make the password generated visible, press the "Visible" button.
|
16
|
+
The "Current" button copies the current password to the primary clipboard.
|
17
|
+
The "Previous" button copies the previous password to the primary clipboard.
|
18
|
+
|
19
|
+
To delete an account, select the account in the entry/combo box, and
|
20
|
+
the press the "Delete Account" button.
|
21
|
+
|
22
|
+
Once one has edited the account, clicking the "Update" button finalizes the record.
|
23
|
+
Note, however, that the change is not yet permanent and saved on disk.
|
24
|
+
Once one is done with all updates, one then needs to press "Save".
|
25
|
+
"Cancel" or closing the window without "Save" will ignore all of the sessions updates.
|
26
|
+
|
27
|
+
The "Change Data File Password" button will allow one the change
|
28
|
+
the master password.
|
29
|
+
|
30
|
+
Right click most anywhere on the app's window for the main menu.
|
31
|
+
"Close" will dock the app and has the same effect as "Cancel".
|
32
|
+
|
33
|
+
Left click on the docked icon to bring back the editor window.
|
34
|
+
|
35
|
+
Right click on the docked icon to select one of the accounts to load
|
36
|
+
the password and username to the clipboard.
|
37
|
+
The password is copied the the primary clipboard and will paste on
|
38
|
+
middle mouse button click.
|
39
|
+
Right click on an entry box to paste the username (via the clipboard's menu).
|
40
|
+
|
41
|
+
Lastly, do not edit
|
42
|
+
~/.gtk2passwordapp-1/passphrase.txt
|
43
|
+
It's used to "salt" the password... without it,
|
44
|
+
one will not be able to decrypt the datafile.
|
45
|
+
|
46
|
+
|
47
|
+
For full documentation and comments, see
|
5
48
|
|
6
49
|
http://ruby-gnome-apps.blogspot.com/search/label/Passwords
|
7
50
|
|
data/bin/gtk2passwordapp
CHANGED
@@ -1,40 +1,29 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
4
|
-
require '
|
5
|
-
GlobalOptionsVariables.set('0.0.8',
|
6
|
-
<<EOT
|
7
|
-
Usage: #{$0.sub(/^.*\//,'')} [options]
|
2
|
+
require 'rubygems'
|
3
|
+
gem 'gtk2applib', '~> 3.1.0'
|
4
|
+
require 'gtk2applib/gtk2_app'
|
8
5
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
require 'lib/setup_user_space'
|
17
|
-
UserSpace.setup
|
18
|
-
UserSpace.copy('/README.txt')
|
19
|
-
require USER_CONF_DIR+CONF_FILE
|
20
|
-
##########################################################
|
21
|
-
require 'lib/gtk2passwordapp'
|
6
|
+
application = {
|
7
|
+
:name => 'Ruby-Gnome Password Manager',
|
8
|
+
:tooltip => 'Password Manager',
|
9
|
+
:FILE => __FILE__,
|
10
|
+
}
|
11
|
+
Gtk2App.init(application)
|
12
|
+
Gtk2App.icon.set_icon_name(Gtk::Stock::DIALOG_AUTHENTICATION)
|
22
13
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
File.unlink(lock) if File.exist?(lock)
|
39
|
-
end
|
14
|
+
require 'gtk2passwordapp/edit_box.rb'
|
15
|
+
require 'gtk2passwordapp/passwords.rb'
|
16
|
+
passwords = Gtk2PasswordApp::Passwords.new
|
17
|
+
Gtk2PasswordApp.build_menu(passwords)
|
18
|
+
|
19
|
+
about = {
|
20
|
+
:authors => ['carlosjhr64@gmail.com'],
|
21
|
+
:comments => 'Ruby-Gtk2 Passsword Manager',
|
22
|
+
:website => 'http://ruby-gnome-apps.blogspot.com/search/label/Passwords',
|
23
|
+
:website_label => 'Ruby-Gnome Password Manager',
|
24
|
+
:license => 'GPL',
|
25
|
+
:copyright => '2009-Dec-14',
|
26
|
+
}
|
27
|
+
Gtk2App.main_window(about) do |window|
|
28
|
+
Gtk2PasswordApp.edit(window,passwords)
|
40
29
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Note: you'll see in ~/.gtk2passwordapp-* a file called passphrase.txt.
|
2
|
+
# Do not edit or delete passphrase, or you'll loose your passwords data.
|
3
|
+
|
4
|
+
module Configuration
|
5
|
+
# Note that the passwords data file name is auto generated, but...
|
6
|
+
# You can place your passwords data file in a directory other than ~/gtk2passwordapp-*
|
7
|
+
PASSWORDS_DATA_DIR = UserSpace::DIRECTORY
|
8
|
+
|
9
|
+
ENTRY_WIDTH = (Gtk2App::HILDON)? 600: 300
|
10
|
+
LABEL_WIDTH = 75
|
11
|
+
GO_BUTTON_LENGTH = 50
|
12
|
+
SPIN_BUTTON_LENGTH = 60
|
13
|
+
PAD = 2 # cell padding
|
14
|
+
|
15
|
+
MAX_PASSWORD_LENGTH = 20
|
16
|
+
DEFAULT_PASSWORD_LENGTH = 7
|
17
|
+
MIN_PASSWORD_LENGTH = 3
|
18
|
+
|
19
|
+
# Switches the roles of PRIMARY and CLIPBOARD when true
|
20
|
+
SWITCH_CLIPBOARDS = Gtk2App::HILDON
|
21
|
+
|
22
|
+
PASSWORD_EXPIRED = 60*60*24*30*3 # 3 months
|
23
|
+
|
24
|
+
URL_PATTERN = Regexp.new('^https?:\/\/[^\s\']+$')
|
25
|
+
|
26
|
+
FONT[:normal] = FONT[:large] = Pango::FontDescription.new( 'Arial 18' ) if Gtk2App::HILDON
|
27
|
+
GUI[:window_size] = [100,100]
|
28
|
+
MENU[:close] = '_Close'
|
29
|
+
end
|
@@ -0,0 +1,328 @@
|
|
1
|
+
module Gtk2PasswordApp
|
2
|
+
include Configuration
|
3
|
+
PRIMARY = Gtk::Clipboard.get((SWITCH_CLIPBOARDS)? Gdk::Selection::CLIPBOARD: Gdk::Selection::PRIMARY)
|
4
|
+
CLIPBOARD = Gtk::Clipboard.get((SWITCH_CLIPBOARDS)? Gdk::Selection::PRIMARY: Gdk::Selection::CLIPBOARD)
|
5
|
+
|
6
|
+
@@index = nil
|
7
|
+
def self.build_menu(passwords)
|
8
|
+
passwords.accounts.each {|account|
|
9
|
+
item = Gtk2App.dock_menu.append_menu_item(account){
|
10
|
+
@@index = passwords.accounts.index(account)
|
11
|
+
PRIMARY.text = passwords.password_of(account)
|
12
|
+
CLIPBOARD.text = passwords.username_of(account)
|
13
|
+
}
|
14
|
+
item.child.modify_fg(Gtk::STATE_NORMAL, RED) if passwords.expired?(account)
|
15
|
+
}
|
16
|
+
Gtk2App.dock_menu.show_all
|
17
|
+
end
|
18
|
+
def self.rebuild_menu(passwords)
|
19
|
+
items = Gtk2App.dock_menu.children
|
20
|
+
3.times{ items.shift } # shift out Quit, Run, and Spacer
|
21
|
+
while item = items.shift do
|
22
|
+
Gtk2App.dock_menu.remove(item)
|
23
|
+
item.destroy
|
24
|
+
end
|
25
|
+
Gtk2PasswordApp.build_menu(passwords)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.get_salt(title='Short Password')
|
29
|
+
dialog = Gtk::Dialog.new(
|
30
|
+
title,
|
31
|
+
nil, nil,
|
32
|
+
[ Gtk::Stock::QUIT, 0 ],
|
33
|
+
[ Gtk::Stock::OK, 1 ])
|
34
|
+
|
35
|
+
label = Gtk::Label.new(title)
|
36
|
+
label.justify = Gtk::JUSTIFY_LEFT
|
37
|
+
label.wrap = true
|
38
|
+
label.modify_font(Configuration::FONT[:normal])
|
39
|
+
dialog.vbox.add(label)
|
40
|
+
entry = Gtk::Entry.new
|
41
|
+
entry.visibility = false
|
42
|
+
entry.modify_font(Configuration::FONT[:normal])
|
43
|
+
dialog.vbox.add(entry)
|
44
|
+
dialog.show_all
|
45
|
+
|
46
|
+
entry.signal_connect('activate'){
|
47
|
+
dialog.response(1)
|
48
|
+
}
|
49
|
+
|
50
|
+
ret = nil
|
51
|
+
dialog.run {|response|
|
52
|
+
ret = entry.text.strip if response == 1
|
53
|
+
}
|
54
|
+
dialog.destroy
|
55
|
+
|
56
|
+
return ret
|
57
|
+
end
|
58
|
+
|
59
|
+
DIALOGS = Gtk2App::Dialogs.new
|
60
|
+
|
61
|
+
EDITOR_LABELS = [
|
62
|
+
:account,
|
63
|
+
:url,
|
64
|
+
:note,
|
65
|
+
:username,
|
66
|
+
:password,
|
67
|
+
]
|
68
|
+
|
69
|
+
|
70
|
+
EDITOR_BUTTONS = [
|
71
|
+
[ :random, :alphanum, :num, :alpha, :caps, ],
|
72
|
+
[ :visibility, :current, :previous, :cancel, :save, :update, ],
|
73
|
+
[ :delete, :cpwd ],
|
74
|
+
]
|
75
|
+
|
76
|
+
TEXT = {
|
77
|
+
# Labels
|
78
|
+
:account => 'Account',
|
79
|
+
:note => 'Note',
|
80
|
+
:password => 'New',
|
81
|
+
# Buttons
|
82
|
+
:username => 'Username',
|
83
|
+
:current => 'Current',
|
84
|
+
:url => 'Url',
|
85
|
+
:note => 'Note',
|
86
|
+
:edit => 'Edit',
|
87
|
+
:update => 'Update',
|
88
|
+
:visibility => 'Visible',
|
89
|
+
:alphanum => 'Alpha-Numeric',
|
90
|
+
:num => 'Numeric',
|
91
|
+
:alpha => 'Letters',
|
92
|
+
:caps => 'All-Caps',
|
93
|
+
:random => 'Random',
|
94
|
+
:previous => 'Previous',
|
95
|
+
:quit => 'Quit',
|
96
|
+
:cancel => 'Cancel',
|
97
|
+
:save => 'Save',
|
98
|
+
:cpwd => 'Change Data File Password',
|
99
|
+
:delete => 'Delete Account',
|
100
|
+
}
|
101
|
+
|
102
|
+
def self.edit(window, passwords)
|
103
|
+
begin
|
104
|
+
dialog_options = {:window=>window}
|
105
|
+
updated = false # only saves data if data updated
|
106
|
+
|
107
|
+
vbox = Gtk::VBox.new
|
108
|
+
window.add(vbox)
|
109
|
+
|
110
|
+
pwdlength = Gtk::SpinButton.new(MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, 1)
|
111
|
+
pwdlength.value = DEFAULT_PASSWORD_LENGTH
|
112
|
+
pwdlength.width_request = SPIN_BUTTON_LENGTH
|
113
|
+
pwdlength.modify_font(FONT[:normal])
|
114
|
+
goto_url = Gtk::Button.new('Go')
|
115
|
+
goto_url.child.modify_font(FONT[:normal])
|
116
|
+
goto_url.width_request = GO_BUTTON_LENGTH
|
117
|
+
|
118
|
+
widget = {}
|
119
|
+
EDITOR_LABELS.each {|s|
|
120
|
+
hbox = Gtk::HBox.new
|
121
|
+
#label = Gtk::Label.new(TEXT[s]+':',hbox)
|
122
|
+
label = Gtk2App::Label.new(TEXT[s]+':',hbox) # Gtk2App's Label
|
123
|
+
#label.modify_font(FONT[:normal])
|
124
|
+
label.width_request = LABEL_WIDTH
|
125
|
+
label.justify = Gtk::JUSTIFY_RIGHT
|
126
|
+
#label.wrap = true
|
127
|
+
widget[s] = (s==:account)? Gtk::ComboBoxEntry.new : Gtk::Entry.new
|
128
|
+
widget[s].width_request = ENTRY_WIDTH -
|
129
|
+
((s == :password)? (SPIN_BUTTON_LENGTH+2*GUI[:padding]):
|
130
|
+
((s == :url)? (GO_BUTTON_LENGTH+2*GUI[:padding]): 0))
|
131
|
+
widget[s].modify_font(FONT[:normal])
|
132
|
+
#hbox.pack_start(label, false, false, GUI[:padding])
|
133
|
+
hbox.pack_start(widget[s], false, false, GUI[:padding])
|
134
|
+
vbox.pack_start(hbox, false, false, GUI[:padding])
|
135
|
+
hbox.pack_start(pwdlength, false, false, GUI[:padding]) if s == :password
|
136
|
+
hbox.pack_start(goto_url, false, false, GUI[:padding]) if s == :url
|
137
|
+
}
|
138
|
+
|
139
|
+
# The go button opens the url in a browser
|
140
|
+
goto_url.signal_connect('clicked'){
|
141
|
+
system("#{APP[:browser]} #{widget[:url].text} > /dev/null 2> /dev/null &")
|
142
|
+
}
|
143
|
+
|
144
|
+
EDITOR_BUTTONS.each{|row|
|
145
|
+
hbox = Gtk::HBox.new
|
146
|
+
row.each {|s|
|
147
|
+
widget[s] = Gtk::Button.new(TEXT[s])
|
148
|
+
widget[s].child.modify_font(FONT[:normal])
|
149
|
+
hbox.pack_start(widget[s], false, false, GUI[:padding])
|
150
|
+
}
|
151
|
+
vbox.pack_start(hbox, false, false, GUI[:padding])
|
152
|
+
}
|
153
|
+
|
154
|
+
# Account
|
155
|
+
passwords.accounts.each { |account|
|
156
|
+
widget[:account].append_text( account )
|
157
|
+
}
|
158
|
+
widget[:account].active = @@index if @@index
|
159
|
+
account_changed = proc {
|
160
|
+
account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
|
161
|
+
if account.length > 0 then
|
162
|
+
widget[:password].text = ''
|
163
|
+
if passwords.include?(account) then
|
164
|
+
widget[:url].text = passwords.url_of(account)
|
165
|
+
widget[:note].text = passwords.note_of(account)
|
166
|
+
widget[:username].text = passwords.username_of(account)
|
167
|
+
else
|
168
|
+
widget[:url].text = ''
|
169
|
+
widget[:note].text = ''
|
170
|
+
widget[:username].text = ''
|
171
|
+
end
|
172
|
+
@@index = widget[:account].active
|
173
|
+
end
|
174
|
+
}
|
175
|
+
account_changed.call
|
176
|
+
widget[:account].signal_connect('changed'){
|
177
|
+
account_changed.call
|
178
|
+
}
|
179
|
+
|
180
|
+
# New Password
|
181
|
+
widget[:password].visibility = false
|
182
|
+
|
183
|
+
# Update
|
184
|
+
widget[:update].signal_connect('clicked'){
|
185
|
+
url = widget[:url].text.strip
|
186
|
+
if url.length == 0 || url =~ URL_PATTERN then
|
187
|
+
account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
|
188
|
+
if account.length > 0 then
|
189
|
+
updated = true if !updated
|
190
|
+
if !passwords.include?(account) then
|
191
|
+
passwords.add(account)
|
192
|
+
@@index = i = passwords.accounts.index(account)
|
193
|
+
widget[:account].insert_text(i,account)
|
194
|
+
end
|
195
|
+
passwords.url_of(account, url)
|
196
|
+
passwords.note_of(account, widget[:note].text.strip)
|
197
|
+
passwords.username_of(account, widget[:username].text.strip)
|
198
|
+
password = widget[:password].text.strip
|
199
|
+
if password.length > 0 then
|
200
|
+
passwords.password_of(account, password) if !passwords.verify?(account, password)
|
201
|
+
widget[:password].text = ''
|
202
|
+
end
|
203
|
+
end
|
204
|
+
else
|
205
|
+
DIALOGS.quick_message('Need url like http://www.site.com/page.html', dialog_options)
|
206
|
+
end
|
207
|
+
}
|
208
|
+
|
209
|
+
# Random
|
210
|
+
widget[:random].signal_connect('clicked'){
|
211
|
+
suggestion = ''
|
212
|
+
pwdlength.value.to_i.times do
|
213
|
+
suggestion += (rand(94)+33).chr
|
214
|
+
end
|
215
|
+
widget[:password].text = suggestion
|
216
|
+
}
|
217
|
+
# Alpha-Numeric
|
218
|
+
widget[:alphanum].signal_connect('clicked'){
|
219
|
+
suggestion = ''
|
220
|
+
while suggestion.length < pwdlength.value.to_i do
|
221
|
+
chr = (rand(75)+48).chr
|
222
|
+
suggestion += chr if chr =~/\w/
|
223
|
+
end
|
224
|
+
widget[:password].text = suggestion
|
225
|
+
}
|
226
|
+
# Numeric
|
227
|
+
widget[:num].signal_connect('clicked'){
|
228
|
+
suggestion = ''
|
229
|
+
pwdlength.value.to_i.times do
|
230
|
+
chr = (rand(10)+48).chr
|
231
|
+
suggestion += chr
|
232
|
+
end
|
233
|
+
widget[:password].text = suggestion
|
234
|
+
}
|
235
|
+
# Letters
|
236
|
+
widget[:alpha].signal_connect('clicked'){
|
237
|
+
suggestion = ''
|
238
|
+
while suggestion.length < pwdlength.value.to_i do
|
239
|
+
chr = (rand(58)+65).chr
|
240
|
+
suggestion += chr if chr =~/[A-Z]/i
|
241
|
+
end
|
242
|
+
widget[:password].text = suggestion
|
243
|
+
}
|
244
|
+
# Caps
|
245
|
+
widget[:caps].signal_connect('clicked'){
|
246
|
+
suggestion = ''
|
247
|
+
pwdlength.value.to_i.times do
|
248
|
+
chr = (rand(26)+65).chr
|
249
|
+
suggestion += chr
|
250
|
+
end
|
251
|
+
widget[:password].text = suggestion
|
252
|
+
}
|
253
|
+
|
254
|
+
# Visibility
|
255
|
+
widget[:visibility].signal_connect('clicked'){
|
256
|
+
widget[:password].visibility = !widget[:password].visibility?
|
257
|
+
}
|
258
|
+
|
259
|
+
# Current
|
260
|
+
widget[:current].signal_connect('clicked'){
|
261
|
+
account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
|
262
|
+
PRIMARY.text = passwords.password_of(account)
|
263
|
+
CLIPBOARD.text = passwords.username_of(account)
|
264
|
+
}
|
265
|
+
|
266
|
+
# Previous
|
267
|
+
widget[:previous].signal_connect('clicked'){
|
268
|
+
account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
|
269
|
+
PRIMARY.text = passwords.previous_password_of(account)
|
270
|
+
CLIPBOARD.text = passwords.username_of(account)
|
271
|
+
}
|
272
|
+
|
273
|
+
# Change Password
|
274
|
+
widget[:cpwd].signal_connect('clicked'){
|
275
|
+
if pwd1 = Gtk2PasswordApp.get_salt('New Password') then
|
276
|
+
if pwd2 = Gtk2PasswordApp.get_salt('Verify') then
|
277
|
+
while !(pwd1==pwd2) do
|
278
|
+
pwd1 = Gtk2PasswordApp.get_salt('Try again!')
|
279
|
+
return if !pwd1
|
280
|
+
pwd2 = Gtk2PasswordApp.get_salt('Verify')
|
281
|
+
return if !pwd2
|
282
|
+
end
|
283
|
+
#@pwd = pwd1
|
284
|
+
passwords.save(pwd1)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
}
|
288
|
+
|
289
|
+
# Save
|
290
|
+
widget[:save].signal_connect('clicked'){
|
291
|
+
if updated then
|
292
|
+
passwords.save
|
293
|
+
updated = false
|
294
|
+
Gtk2PasswordApp.rebuild_menu(passwords)
|
295
|
+
end
|
296
|
+
Gtk2App.close
|
297
|
+
}
|
298
|
+
|
299
|
+
# Delete
|
300
|
+
widget[:delete].signal_connect('clicked'){
|
301
|
+
account = (widget[:account].active_text)? widget[:account].active_text.strip: nil
|
302
|
+
if account then
|
303
|
+
i = passwords.accounts.index(account)
|
304
|
+
if i then
|
305
|
+
passwords.delete(account)
|
306
|
+
widget[:account].remove_text(i)
|
307
|
+
@@index = (widget[:account].active = (i > 0)? i - 1: 0)
|
308
|
+
updated = true
|
309
|
+
DIALOGS.quick_message("#{account} deleted.", dialog_options)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
}
|
313
|
+
|
314
|
+
# Cancel
|
315
|
+
widget[:cancel].signal_connect('clicked'){ Gtk2App.close }
|
316
|
+
window.signal_connect('destroy'){
|
317
|
+
if updated then
|
318
|
+
passwords.load # revert
|
319
|
+
updated = false
|
320
|
+
end
|
321
|
+
}
|
322
|
+
|
323
|
+
window.show_all
|
324
|
+
rescue Exception
|
325
|
+
puts_bang!
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'rubygems'
|
3
|
+
begin
|
4
|
+
require 'crypt_tea'
|
5
|
+
rescue Exception
|
6
|
+
# above is what works, but documentation shows this...
|
7
|
+
require 'crypt-tea'
|
8
|
+
end
|
9
|
+
|
10
|
+
module Gtk2PasswordApp
|
11
|
+
class IOCrypt
|
12
|
+
LENGTH = 15
|
13
|
+
|
14
|
+
def initialize(passphrase)
|
15
|
+
@key = Crypt::XXTEA.new(passphrase[0..LENGTH])
|
16
|
+
end
|
17
|
+
|
18
|
+
def load(dumpfile)
|
19
|
+
data = nil
|
20
|
+
File.open(dumpfile,'r'){|fh| data = YAML.load( @key.decrypt( fh.read ) ) }
|
21
|
+
return data
|
22
|
+
end
|
23
|
+
|
24
|
+
def dump(dumpfile, data)
|
25
|
+
count = nil
|
26
|
+
File.open(dumpfile,'w') { |fh| count = fh.write( @key.encrypt( YAML.dump( data ) ) ) }
|
27
|
+
return count
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'gtk2passwordapp/passwords_data.rb'
|
2
|
+
module Gtk2PasswordApp
|
3
|
+
class Passwords < PasswordsData
|
4
|
+
|
5
|
+
def _create_passphrase
|
6
|
+
passphrase = ''
|
7
|
+
|
8
|
+
IOCrypt::LENGTH.times do
|
9
|
+
passphrase += (rand(94)+33).chr
|
10
|
+
end
|
11
|
+
File.open(@pfile,'w'){|fh| fh.write passphrase }
|
12
|
+
File.chmod(0600, @pfile)
|
13
|
+
|
14
|
+
return passphrase
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_passphrase(mv=false)
|
18
|
+
passphrase = ''
|
19
|
+
|
20
|
+
@pfile = UserSpace::DIRECTORY+'/passphrase.txt'
|
21
|
+
if mv then
|
22
|
+
File.rename(@pfile, @pfile+'.bak') if File.exist?(@pfile)
|
23
|
+
passphrase = _create_passphrase
|
24
|
+
else
|
25
|
+
if File.exist?(@pfile) then
|
26
|
+
File.open(@pfile,'r'){|fh| passphrase = fh.read }
|
27
|
+
else
|
28
|
+
passphrase = _create_passphrase(@pfile)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
return passphrase
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_datafile?
|
36
|
+
Find.find(UserSpace::DIRECTORY){|fn|
|
37
|
+
Find.prune if !(fn==UserSpace::DIRECTORY) && File.directory?(fn)
|
38
|
+
if fn =~/[0123456789abcdef]{32}\.dat$/ then
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
}
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :pfile
|
46
|
+
def initialize
|
47
|
+
@pwd = Gtk2PasswordApp.get_salt || exit
|
48
|
+
@pfile = nil
|
49
|
+
@pph = get_passphrase
|
50
|
+
super(@pwd+@pph)
|
51
|
+
# Password file exist?
|
52
|
+
if self.exist? # then
|
53
|
+
# Yes, load passwords file.
|
54
|
+
self.load
|
55
|
+
else
|
56
|
+
# No, check if there is a file....
|
57
|
+
if has_datafile? # then
|
58
|
+
# Yes, it's got a datafile. Ask for password again.
|
59
|
+
while !self.exist? do
|
60
|
+
@pwd = Gtk2PasswordApp.get_salt('Try again!') || exit
|
61
|
+
super(@pwd+@pph)
|
62
|
+
end
|
63
|
+
self.load
|
64
|
+
else
|
65
|
+
# Else, must be a new install.
|
66
|
+
pwd = @pwd
|
67
|
+
@pwd = Gtk2PasswordApp.get_salt('Verify New Password') || exit
|
68
|
+
while !(pwd == @pwd) do
|
69
|
+
pwd = Gtk2PasswordApp.get_salt('Try again!') || exit
|
70
|
+
@pwd = Gtk2PasswordApp.get_salt('Verify New Password') || exit
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# Off to the races...
|
75
|
+
end
|
76
|
+
|
77
|
+
def save(pwd=nil)
|
78
|
+
if pwd then
|
79
|
+
pfbak = self.pfile + '.bak'
|
80
|
+
pph = get_passphrase(true) # new passphrase
|
81
|
+
dfbak = self.dumpfile + '.bak'
|
82
|
+
super(pwd+pph)
|
83
|
+
@pwd = pwd
|
84
|
+
@pph = pph
|
85
|
+
File.unlink(pfbak) if File.exist?(pfbak)
|
86
|
+
File.unlink(dfbak) if File.exist?(dfbak)
|
87
|
+
else
|
88
|
+
super()
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
|
2
|
-
require 'lib/iocrypt'
|
1
|
+
require 'gtk2passwordapp/iocrypt'
|
3
2
|
require 'digest/md5'
|
4
3
|
|
4
|
+
module Gtk2PasswordApp
|
5
5
|
class PasswordsData
|
6
6
|
include Configuration
|
7
7
|
attr_accessor :account
|
8
|
-
attr_reader :data
|
8
|
+
attr_reader :data, :dumpfile
|
9
9
|
|
10
10
|
PASSWORD = 0
|
11
11
|
PREVIOUS = 1
|
@@ -16,13 +16,8 @@ class PasswordsData
|
|
16
16
|
|
17
17
|
def _reset(passphrase)
|
18
18
|
raise "Need a good passphrase" if !passphrase || passphrase.length < 7
|
19
|
-
@passphrase = passphrase[0..
|
19
|
+
@passphrase = passphrase[0..IOCrypt::LENGTH]
|
20
20
|
@dumpfile = PASSWORDS_DATA_DIR + '/' + Digest::MD5.hexdigest(@passphrase) + '.dat'
|
21
|
-
@online = (@dumpfile =~ /^http:\/\//)? true: false
|
22
|
-
end
|
23
|
-
|
24
|
-
def online?
|
25
|
-
@online
|
26
21
|
end
|
27
22
|
|
28
23
|
def initialize(passphrase)
|
@@ -31,19 +26,16 @@ class PasswordsData
|
|
31
26
|
end
|
32
27
|
|
33
28
|
def exist?
|
34
|
-
raise "n/a online" if @online
|
35
29
|
File.exist?(@dumpfile)
|
36
30
|
end
|
37
31
|
|
38
32
|
def load(passphrase = nil)
|
39
33
|
_reset(passphrase) if passphrase
|
40
|
-
raise "Wrong passphrase" if !@online && !exist?
|
41
34
|
iocrypt = IOCrypt.new(@passphrase)
|
42
35
|
@data = iocrypt.load(@dumpfile)
|
43
36
|
end
|
44
37
|
|
45
38
|
def save(passphrase = nil)
|
46
|
-
raise "n/a online" if @online
|
47
39
|
# just in case, keep a backup
|
48
40
|
File.rename(@dumpfile, @dumpfile+'.bak') if File.exist?(@dumpfile)
|
49
41
|
_reset(passphrase) if passphrase
|
@@ -53,7 +45,6 @@ class PasswordsData
|
|
53
45
|
end
|
54
46
|
|
55
47
|
def add(account)
|
56
|
-
raise "n/a online" if @online
|
57
48
|
raise "Pre-existing" if @data[account]
|
58
49
|
raise "Can't have nil account" if !account
|
59
50
|
@data[account] = ['','','','','']
|
@@ -72,7 +63,6 @@ class PasswordsData
|
|
72
63
|
end
|
73
64
|
|
74
65
|
def delete(account)
|
75
|
-
raise "n/a online" if @online
|
76
66
|
raise "#{account} not found" if !@data[account]
|
77
67
|
@data.delete(account)
|
78
68
|
end
|
@@ -117,3 +107,4 @@ class PasswordsData
|
|
117
107
|
return @data[account][USERNAME] || ''
|
118
108
|
end
|
119
109
|
end
|
110
|
+
end
|
data/pngs/logo.png
ADDED
Binary file
|