passvault 0.1.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.
- data/.gitignore +3 -0
- data/Laurent_Vansteenberghe.zip +0 -0
- data/README.md +65 -0
- data/bin/passvault +3 -0
- data/bin/passvault-gui +3 -0
- data/lib/bytes_manip.rb +47 -0
- data/lib/conn_cmds.rb +337 -0
- data/lib/conn_constants.rb +116 -0
- data/lib/conn_init.rb +92 -0
- data/lib/conn_transmit.rb +96 -0
- data/lib/connection.rb +28 -0
- data/lib/console_ui.rb +243 -0
- data/lib/crypto.rb +146 -0
- data/lib/file_access.rb +103 -0
- data/lib/gui.rb +270 -0
- data/lib/key_settings.rb +124 -0
- data/lib/rights.rb +6 -0
- data/lib/vault.rb +357 -0
- data/passvault.gemspec +30 -0
- data/pres/secu.pdf +0 -0
- data/pres/secu.pptx +0 -0
- metadata +136 -0
data/lib/crypto.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require_relative 'bytes_manip'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
# This class contains the implementation of cryptographics utilities and functions.
|
5
|
+
#
|
6
|
+
# All keys used to communicate with the tag (Triple DES keys) are always
|
7
|
+
# represented as 16 bytes long byte-strings. Byte-strings is what the OpenSSL
|
8
|
+
# library use, so it makes sense to use that. But often, it is useful to
|
9
|
+
# manipulate those as array of bytes. The functions String.unpack() and
|
10
|
+
# Array.pack() ((un)pack into(from) a byte string) are very useful for this,
|
11
|
+
# especially with 'C*' as argument .
|
12
|
+
#
|
13
|
+
# The card is only able to perform TDES encryption (not decryption) with 2 keys
|
14
|
+
# (keying option 2, aka 2TDEA). When the two keys are identical, the result is
|
15
|
+
# the same as plain DES. We elected to use only TDES on the user end (which is
|
16
|
+
# presumably also the case on the tag's end). Therefore all TDES and DES keys
|
17
|
+
# are 16-byte key. A 16 byte DES key is formed by concatening the 8 byte DES key
|
18
|
+
# to itself.
|
19
|
+
#
|
20
|
+
# AES encryption is used to encrypt the credentials stored on the tag.
|
21
|
+
#
|
22
|
+
# OpenSSL modes:
|
23
|
+
#
|
24
|
+
# <tt>DES-EDE-CBC</tt> = Triple DES (Encrypt Decrypt Encrypt) in the regular CBC
|
25
|
+
# mode (send for encryption, receive for decryption). The IV is zeroed by
|
26
|
+
# default.
|
27
|
+
#
|
28
|
+
# <tt>AES-256-CBC</tt> = AES with 256 bit keys in the regular CBC mode. The IV
|
29
|
+
# is zeroed by default.
|
30
|
+
|
31
|
+
module Crypto
|
32
|
+
include BytesManipulation
|
33
|
+
|
34
|
+
# Block length for DES.
|
35
|
+
DES_BLEN = 8
|
36
|
+
|
37
|
+
# An IV of +DES_BLEN+ zeroes.
|
38
|
+
ZERO_IV = Array.new(DES_BLEN, 0).pack("C*")
|
39
|
+
|
40
|
+
# Size of the HMAC (SHA-256) digest.
|
41
|
+
HMAC_LEN = 32
|
42
|
+
|
43
|
+
# Deciphers a byte string using 3DES in CBC receive mode. Returns a byte
|
44
|
+
# string.
|
45
|
+
def decipher_receive(bytes, key)
|
46
|
+
des = OpenSSL::Cipher.new('DES-EDE-CBC')
|
47
|
+
des.decrypt
|
48
|
+
des.key = key
|
49
|
+
des.padding = 0 # no padding
|
50
|
+
return des.update(bytes) + des.final
|
51
|
+
end
|
52
|
+
|
53
|
+
# Deciphers a byte string using 3DES in CBC send mode.
|
54
|
+
def decipher_send(bytes, key)
|
55
|
+
mix = ZERO_IV
|
56
|
+
partial_block_count = (bytes.bytesize % DES_BLEN > 0) ? 1 : 0
|
57
|
+
nblocks = bytes.length / DES_BLEN + partial_block_count
|
58
|
+
out = Array.new(nblocks)
|
59
|
+
|
60
|
+
out.each_index do |i|
|
61
|
+
block = bytes.byteslice(i*DES_BLEN, DES_BLEN)
|
62
|
+
block = bs_xor(block, mix)
|
63
|
+
block = decipher_receive(block, key)
|
64
|
+
out[i] = block
|
65
|
+
mix = block
|
66
|
+
end
|
67
|
+
return out.reduce("", :+)
|
68
|
+
end
|
69
|
+
|
70
|
+
# The following enciphering functions are not needed by the passworld vault,
|
71
|
+
# but are always nice to have to perform some verifications.
|
72
|
+
|
73
|
+
# Enciphers a byte string using 3DES in CBC send mode. Returns a byte
|
74
|
+
# string. Unused.
|
75
|
+
def encipher_send(bytes, key)
|
76
|
+
des = OpenSSL::Cipher.new('DES-EDE-CBC')
|
77
|
+
des.encrypt
|
78
|
+
des.key = key
|
79
|
+
des.padding = 0 # no padding
|
80
|
+
return des.update(bytes) + des.final
|
81
|
+
end
|
82
|
+
|
83
|
+
# Enciphers a byte string using 3DES in CBC receive mode. Returns a byte
|
84
|
+
# string. Unused.
|
85
|
+
def encipher_receive(bytes, key)
|
86
|
+
mix = ZERO_IV
|
87
|
+
partial_block_count = (bytes.bytesize % DES_BLEN > 0) ? 1 : 0
|
88
|
+
nblocks = bytes.length / DES_BLEN + partial_block_count
|
89
|
+
out = Array.new(nblocks)
|
90
|
+
|
91
|
+
out.each_index do |i|
|
92
|
+
block1 = bytes.byteslice(i*DES_BLEN, DES_BLEN)
|
93
|
+
block = encipher_send(block1, key)
|
94
|
+
block = bs_xor(block, mix)
|
95
|
+
out[i] = block
|
96
|
+
mix = block1
|
97
|
+
end
|
98
|
+
return out.reduce("", :+)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Derive a key of the given size using the PBKDF2 derivation function, from
|
102
|
+
# given pass and salt. 50k iterations of SHA-256 are used.
|
103
|
+
def derive_key(pass, salt, key_size)
|
104
|
+
sha256_d = OpenSSL::Digest::SHA256.new
|
105
|
+
# (pass, salt, iter, keylength, digest)
|
106
|
+
return OpenSSL::PKCS5::pbkdf2_hmac(pass, salt, 50000, key_size, sha256_d)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Derive a DESFire Triple DES key. This is similar to <tt>derive_key()</tt>
|
110
|
+
# with +key_size+ = 16, excepted that we overwrite the parity bits of the
|
111
|
+
# key. Anyone can ask to get those bits from the tag ("key versionning"
|
112
|
+
# feature). As such, keeping the orignal parity bits makes the key less
|
113
|
+
# secure.
|
114
|
+
def derive_desfire_key(pass, salt)
|
115
|
+
key = derive_key(pass, salt, 16)
|
116
|
+
array = key.unpack('C*')
|
117
|
+
array.map! { |e| e & 0xFE }
|
118
|
+
return array.pack('C*')
|
119
|
+
end
|
120
|
+
|
121
|
+
# Enciphers using AES with 256 bits key in CBC (send) mode.
|
122
|
+
def aes_encipher(bytes, key)
|
123
|
+
aes = OpenSSL::Cipher.new('AES-256-CBC')
|
124
|
+
aes.encrypt
|
125
|
+
aes.key = key
|
126
|
+
aes.padding = 0 # no padding
|
127
|
+
return aes.update(bytes) + aes.final
|
128
|
+
end
|
129
|
+
|
130
|
+
# Deciphers using AES with 256 bits key in CBC (receive) mode.
|
131
|
+
def aes_decipher(bytes, key)
|
132
|
+
aes = OpenSSL::Cipher.new('AES-256-CBC')
|
133
|
+
aes.decrypt
|
134
|
+
aes.key = key
|
135
|
+
aes.padding = 0 # no padding
|
136
|
+
return aes.update(bytes) + aes.final
|
137
|
+
end
|
138
|
+
|
139
|
+
# Computes a HMAC digest on given data using given key. Uses SHA-1 as
|
140
|
+
# digest.
|
141
|
+
def hmac(bytes, key)
|
142
|
+
sha256_d = sha256_d = OpenSSL::Digest::SHA256.new
|
143
|
+
return OpenSSL::HMAC.digest(sha256_d, key, bytes)
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
data/lib/file_access.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
module Rights
|
2
|
+
|
3
|
+
# Communication setting: plain (unencrypted, unauthenticated) communication.
|
4
|
+
CS_PLAIN = 0x0
|
5
|
+
|
6
|
+
# Communication setting: unencrypted, authenticated communication.
|
7
|
+
CS_PLAIN_MAC = 0x1
|
8
|
+
|
9
|
+
# Communication setting: encrypted, authenticated communication.
|
10
|
+
CS_CIPHERED = 0x3
|
11
|
+
|
12
|
+
# Used in file access rights in place of a key number, to allow unauthenticated
|
13
|
+
# access.
|
14
|
+
FREE_ACCESS = 0xE
|
15
|
+
|
16
|
+
# Used in file access rights in place of a key number, to indicate that no key
|
17
|
+
# is authorized.
|
18
|
+
DENY_ACCESS = 0xF
|
19
|
+
|
20
|
+
# The file access settings determine the keys needed to authenticate in order to
|
21
|
+
# perform some operations on files (inside an application), and how the file
|
22
|
+
# transfer are performed. Those two separate concepts are called in the DESFire
|
23
|
+
# documentation "file access rights" and "communication settings".
|
24
|
+
#
|
25
|
+
# The keys can be a key number from the application (from 0x0 to 0xD) or the
|
26
|
+
# constants +FREE_ACCESS+ (no authentication needed) or +DENY_ACCESS+ (the operation
|
27
|
+
# cannot be performed) (constants defined in the +Rights+ module).
|
28
|
+
class FileAccess
|
29
|
+
|
30
|
+
# Key to be able to read.
|
31
|
+
attr_reader :read
|
32
|
+
|
33
|
+
# Key to be able to write.
|
34
|
+
attr_reader :write
|
35
|
+
|
36
|
+
# Key to be able to read and write.
|
37
|
+
attr_reader :rw
|
38
|
+
|
39
|
+
# Key to change the file access rights.
|
40
|
+
attr_reader :change
|
41
|
+
|
42
|
+
# Do the data transfer to/from the file need to authenticated or ciphered? The
|
43
|
+
# valid values are the <tt>CS_...</tt> constants defined in the +Rights+
|
44
|
+
# module. +com_set+ stands for "communication settings".
|
45
|
+
attr_reader :com_set
|
46
|
+
|
47
|
+
# The constructor takes a hash of parameters, which correspond to the class
|
48
|
+
# attributes. See the class documentation for an explanation of valid key
|
49
|
+
# values.
|
50
|
+
def initialize(hash={})
|
51
|
+
@read = hash[:read] || DENY_ACCESS
|
52
|
+
@write = hash[:write] || DENY_ACCESS
|
53
|
+
@rw = hash[:rw] || DENY_ACCESS
|
54
|
+
@change = hash[:change] || DENY_ACCESS
|
55
|
+
@com_set = hash[:com_set] || CS_CIPHERED
|
56
|
+
end
|
57
|
+
|
58
|
+
# Builds and returns a +FileAccess+ object from the byte array returned by the
|
59
|
+
# +GET_FILE_SETTINGS+ command.
|
60
|
+
def self.from_a(ba)
|
61
|
+
FileAccess.new(
|
62
|
+
:read => ba[3] >> 4,
|
63
|
+
:write => ba[3] % 16,
|
64
|
+
:rw => ba[2] >> 4,
|
65
|
+
:change => ba[2] % 16,
|
66
|
+
:com_set => ba[1])
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns a two-byte little-endian view of the access rights (not the
|
70
|
+
# communication settings), fit to be used as a command parameter.
|
71
|
+
def to_a
|
72
|
+
[rw*16 + change, read*16 + write]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Can the file be read when authenticated with the given key, and how? Returns
|
76
|
+
# either +false+ or a <tt>CS_...</tt> constant (see +Rights+ module
|
77
|
+
# documentation).
|
78
|
+
def read_type(key_no)
|
79
|
+
if @read == key_no or @rw == key_no
|
80
|
+
return @com_set
|
81
|
+
elsif @read == FREE_ACCESS or @rw == FREE_ACCESS
|
82
|
+
return CS_PLAIN
|
83
|
+
else
|
84
|
+
return false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Can the file be written when authenticated with the given key, and how?
|
89
|
+
# Returns either +false+ or a <tt>CS_...</tt> constant (see +Rights+ module
|
90
|
+
# documentation).
|
91
|
+
def write_type(key_no)
|
92
|
+
if @write == key_no or @rw == key_no
|
93
|
+
return @com_set
|
94
|
+
elsif @write == FREE_ACCESS or @rw == FREE_ACCESS
|
95
|
+
return CS_PLAIN
|
96
|
+
else
|
97
|
+
return false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/lib/gui.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
require "wx"
|
2
|
+
require_relative 'vault'
|
3
|
+
include Wx
|
4
|
+
|
5
|
+
module VaultGUI
|
6
|
+
TIMEOUT = 300
|
7
|
+
|
8
|
+
# Initialize the vault
|
9
|
+
@@vault = Vault.new
|
10
|
+
|
11
|
+
# Return the vault associated to the GUI.
|
12
|
+
def self.vault
|
13
|
+
return @@vault
|
14
|
+
end
|
15
|
+
|
16
|
+
class GUILauncher < App
|
17
|
+
def on_init
|
18
|
+
GUI.new.show
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class GUI < Frame
|
23
|
+
def initialize
|
24
|
+
super(nil, 0, "Password Vault GUI",
|
25
|
+
:style => DEFAULT_FRAME_STYLE ^ RESIZE_BORDER ^ MAXIMIZE_BOX)
|
26
|
+
|
27
|
+
ask_password()
|
28
|
+
|
29
|
+
vbox = BoxSizer.new(VERTICAL)
|
30
|
+
pl = PasswordList.new(self)
|
31
|
+
evt_list_item_activated(pl) { |event|
|
32
|
+
item_activated(pl.get_item(event.get_index, 0).get_text())
|
33
|
+
}
|
34
|
+
panel = Panel.new(self)
|
35
|
+
|
36
|
+
hbox = BoxSizer.new(HORIZONTAL)
|
37
|
+
add_button = Button.new(panel, :label=>"ADD", :size => [80, 20])
|
38
|
+
evt_button(add_button) {
|
39
|
+
check_and_reset_timer()
|
40
|
+
add_pushed(pl)
|
41
|
+
}
|
42
|
+
remove_button = Button.new(panel, :label=>"REMOVE", :size => [80, 20])
|
43
|
+
evt_button(remove_button) {
|
44
|
+
check_and_reset_timer()
|
45
|
+
remove_pushed(pl)
|
46
|
+
}
|
47
|
+
edit_button = Button.new(panel, :label=>"EDIT", :size => [80, 20])
|
48
|
+
evt_button(edit_button) {
|
49
|
+
check_and_reset_timer()
|
50
|
+
edit_pushed(pl)
|
51
|
+
}
|
52
|
+
hbox.add(add_button)
|
53
|
+
hbox.add(remove_button)
|
54
|
+
hbox.add(edit_button)
|
55
|
+
panel.set_sizer(hbox)
|
56
|
+
|
57
|
+
vbox.add(pl)
|
58
|
+
vbox.add(panel)
|
59
|
+
self.set_sizer(vbox)
|
60
|
+
vbox.set_size_hints( self )
|
61
|
+
self.centre
|
62
|
+
|
63
|
+
evt_close() { |event|
|
64
|
+
VaultGUI.vault.destroy
|
65
|
+
self.destroy
|
66
|
+
}
|
67
|
+
|
68
|
+
@timer = Time.now.to_i
|
69
|
+
end
|
70
|
+
|
71
|
+
def ask_password()
|
72
|
+
password_popup = PasswordPopup.new(self)
|
73
|
+
if password_popup.show_modal == ID_OK
|
74
|
+
pass_candidate = password_popup.get_value()
|
75
|
+
begin
|
76
|
+
VaultGUI.vault.authenticate(pass_candidate)
|
77
|
+
rescue => e
|
78
|
+
Kernel.raise e unless e.message == Vault::AUTHENTICATION_ERROR
|
79
|
+
ErrorPopup.new("Error: wrong password!", "Error: wrong password!", true)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
Kernel.exit
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def check_and_reset_timer()
|
87
|
+
now = Time.now.to_i
|
88
|
+
if now - @timer > TIMEOUT
|
89
|
+
VaultGUI.vault.deauthenticate()
|
90
|
+
ask_password()
|
91
|
+
end
|
92
|
+
@timer = now
|
93
|
+
end
|
94
|
+
|
95
|
+
def item_activated(name)
|
96
|
+
info_popup = ViewPopup.new(self)
|
97
|
+
loginPassword = VaultGUI.vault.credential(name)
|
98
|
+
info_popup.set_name_value(name)
|
99
|
+
info_popup.set_login_value(loginPassword[0])
|
100
|
+
info_popup.set_password_value(loginPassword[1])
|
101
|
+
info_popup.show
|
102
|
+
evt_button(info_popup) {
|
103
|
+
info_popup.destroy
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_pushed(pl)
|
108
|
+
add_popup = AddPopup.new(self)
|
109
|
+
add_popup.show
|
110
|
+
evt_button(add_popup) {
|
111
|
+
new_name = add_popup.get_name_value
|
112
|
+
new_login = add_popup.get_login_value
|
113
|
+
new_password = add_popup.get_password_value
|
114
|
+
begin
|
115
|
+
VaultGUI.vault.add(new_name, new_login, new_password)
|
116
|
+
pl.insert_item(ListItem.new)
|
117
|
+
add_popup.destroy
|
118
|
+
rescue => e
|
119
|
+
display_error_if_needed(e)
|
120
|
+
add_popup.destroy
|
121
|
+
end
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
def remove_pushed(pl)
|
126
|
+
selection = pl.get_selections
|
127
|
+
if selection.length > 0
|
128
|
+
index_deleted = selection[0]
|
129
|
+
deleted = pl.get_item(index_deleted, 0).get_text
|
130
|
+
pl.delete_item(index_deleted)
|
131
|
+
VaultGUI.vault.remove(deleted)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def edit_pushed(pl)
|
136
|
+
selection = pl.get_selections
|
137
|
+
if selection.length > 0
|
138
|
+
index_edited = selection[0]
|
139
|
+
edit_popup = EditPopup.new(self)
|
140
|
+
edit_popup.show
|
141
|
+
evt_button(edit_popup) {
|
142
|
+
name = pl.get_item(index_edited, 0).get_text
|
143
|
+
new_login = edit_popup.get_login_value
|
144
|
+
new_password = edit_popup.get_password_value
|
145
|
+
begin
|
146
|
+
VaultGUI.vault.edit(name, new_login, new_password)
|
147
|
+
edit_popup.destroy
|
148
|
+
rescue => e
|
149
|
+
display_error_if_needed(e)
|
150
|
+
end
|
151
|
+
}
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def display_error_if_needed(e)
|
156
|
+
if e.class == Vault::CredentialError
|
157
|
+
ErrorPopup.new(e.message, "Credential error", false)
|
158
|
+
else
|
159
|
+
Kernel.raise e
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class PasswordList < ListCtrl
|
165
|
+
def initialize(parent)
|
166
|
+
super(parent,
|
167
|
+
:style=>LC_REPORT | LC_VIRTUAL | LC_SINGLE_SEL,
|
168
|
+
:size=>[240, 240])
|
169
|
+
insert_column(0, "Name")
|
170
|
+
set_column_width(0, 240)
|
171
|
+
set_item_count(VaultGUI.vault.credentials_names.count)
|
172
|
+
end
|
173
|
+
|
174
|
+
def on_get_item_text(item, column)
|
175
|
+
return VaultGUI.vault.credentials_names.to_a[item]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class ErrorPopup < MessageDialog
|
180
|
+
def initialize (message, caption, kill)
|
181
|
+
super(nil,
|
182
|
+
:message => message,
|
183
|
+
:caption => caption,
|
184
|
+
:style => OK | ICON_ERROR)
|
185
|
+
if self.show_modal == ID_OK && kill
|
186
|
+
VaultGUI.vault.destroy
|
187
|
+
Kernel.exit
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class PasswordPopup < TextEntryDialog
|
193
|
+
def initialize(parent)
|
194
|
+
super(parent,
|
195
|
+
:message => "Enter your password:",
|
196
|
+
:caption => "Enter your password",
|
197
|
+
:style => OK | TE_PASSWORD)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
class InfoPopup < Frame
|
202
|
+
def initialize(parent, title, edit_name_tf, edit_login_tf, edit_password_tf)
|
203
|
+
super(parent, 0, title,
|
204
|
+
:style => DEFAULT_FRAME_STYLE ^ RESIZE_BORDER ^ MAXIMIZE_BOX)
|
205
|
+
panel = Panel.new(self)
|
206
|
+
namel = StaticText.new(panel, 0, "Name", :style => ALIGN_CENTER)
|
207
|
+
@name_tf = TextCtrl.new(panel, 0)
|
208
|
+
@name_tf.set_editable(edit_name_tf)
|
209
|
+
loginl = StaticText.new(panel, 0, "Login", :style => ALIGN_CENTER)
|
210
|
+
@login_tf = TextCtrl.new(panel, 0)
|
211
|
+
@login_tf.set_editable(edit_login_tf)
|
212
|
+
password_l = StaticText.new(panel, 0, "Password", :style => ALIGN_CENTER)
|
213
|
+
@password_tf = TextCtrl.new(panel, 0, :style => TE_PASSWORD)
|
214
|
+
@password_tf.set_editable(edit_password_tf)
|
215
|
+
okb = Button.new(panel, 0, "OK")
|
216
|
+
vbox = BoxSizer.new(VERTICAL)
|
217
|
+
vbox.add(namel)
|
218
|
+
vbox.add(@name_tf)
|
219
|
+
vbox.add(loginl)
|
220
|
+
vbox.add(@login_tf)
|
221
|
+
vbox.add(password_l)
|
222
|
+
vbox.add(@password_tf)
|
223
|
+
vbox.add(okb)
|
224
|
+
panel.set_sizer(vbox)
|
225
|
+
self.centre
|
226
|
+
end
|
227
|
+
|
228
|
+
def get_name_value()
|
229
|
+
return @name_tf.get_value
|
230
|
+
end
|
231
|
+
|
232
|
+
def get_login_value()
|
233
|
+
return @login_tf.get_value
|
234
|
+
end
|
235
|
+
|
236
|
+
def get_password_value()
|
237
|
+
return @password_tf.get_value
|
238
|
+
end
|
239
|
+
|
240
|
+
def set_name_value(name)
|
241
|
+
@name_tf.set_value(name)
|
242
|
+
end
|
243
|
+
|
244
|
+
def set_login_value(login)
|
245
|
+
@login_tf.set_value(login)
|
246
|
+
end
|
247
|
+
|
248
|
+
def set_password_value(password)
|
249
|
+
@password_tf.set_value(password)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
class EditPopup < InfoPopup
|
254
|
+
def initialize(parent)
|
255
|
+
super(parent, "Edit entry", false, true, true)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class AddPopup < InfoPopup
|
260
|
+
def initialize(parent)
|
261
|
+
super(parent, "Add new entry", true, true, true)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
class ViewPopup < InfoPopup
|
266
|
+
def initialize(parent)
|
267
|
+
super(parent, "View entry", false, false, false)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|