gtk2passwordapp 0.0.8 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|