"Fossies" - the Fresh Open Source Software Archive 
Member "revelation-0.5.4/src/lib/datahandler/fpm.py" (4 Oct 2020, 8598 Bytes) of package /linux/privat/revelation-0.5.4.tar.xz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "fpm.py" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
0.5.3_vs_0.5.4.
1 #
2 # Revelation - a password manager for GNOME 2
3 # http://oss.codepoet.no/revelation/
4 # $Id$
5 #
6 # Module for handling Figaro's Password Manager data
7 #
8 #
9 # Copyright (c) 2003-2006 Erik Grinaker
10 #
11 # This program is free software; you can redistribute it and/or
12 # modify it under the terms of the GNU General Public License
13 # as published by the Free Software Foundation; either version 2
14 # of the License, or (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #
25
26 from . import base
27 from revelation import data, entry, util
28
29 import math, random, string, xml.dom.minidom
30
31 from xml.parsers.expat import ExpatError
32 from Cryptodome.Cipher import Blowfish
33 from Cryptodome.Hash import MD5
34 import Cryptodome.Random as Random
35
36
37 class FPM(base.DataHandler):
38 "Data handler for Figaro's Password Manager data"
39
40 name = "Figaro's Password Manager"
41 importer = True
42 exporter = True
43 encryption = True
44
45
46 def __init__(self):
47 base.DataHandler.__init__(self)
48
49
50 def __decrypt(self, cipher, data):
51 "Decrypts data"
52
53 if isinstance(data, str):
54 data = data.encode()
55
56 # decode ascii armoring
57 decoded = b""
58
59 for i in range(len(data) // 2):
60 high = data[2 * i] - ord("a")
61 low = data[2 * i + 1] - ord("a")
62 decoded += bytes((high * 16 + low,))
63 data = decoded
64
65 # decrypt data
66 data = cipher.decrypt(data)
67
68 # unrotate field
69 blocks = int(math.ceil(len(data) / float(8)))
70 plain = b""
71
72 for offset in range(8):
73 for block in range(blocks):
74 plain += bytes((data[block * 8 + offset],))
75
76 return plain.split(b"\x00")[0]
77
78
79 def __encrypt(self, cipher, data):
80 "Encrypts data"
81
82 # get data sizes
83 blocks = (len(data) // 7) + 1
84 size = 8 * blocks
85
86 # add noise
87 rand = Random.new()
88 data += b'\x00' + rand.read(size - len(data) - 1)
89
90 # rotate data
91 rotated = b""
92 for block in range(blocks):
93 for offset in range(8):
94 rotated += bytes((data[offset * blocks + block],))
95
96 data = rotated
97
98 # encrypt data
99 data = cipher.encrypt(data)
100
101 # ascii-armor data
102 res = b""
103
104 for i in range(len(data)):
105 high = data[i] // 16
106 low = data[i] - high * 16
107 res += bytes((ord("a") + high, ord("a") + low))
108
109 data = res
110
111
112 return data
113
114
115 def check(self, input):
116 "Checks if the data is valid"
117
118 try:
119 if input is None:
120 raise base.FormatError
121
122 dom = xml.dom.minidom.parseString(input.strip())
123
124 if dom.documentElement.nodeName != "FPM":
125 raise base.FormatError
126
127 minversion = dom.documentElement.attributes["min_version"].nodeValue
128
129 if int(minversion.split(".")[1]) > 58:
130 raise base.VersionError
131
132
133 except ExpatError:
134 raise base.FormatError
135
136 except ( KeyError, IndexError ):
137 raise base.FormatError
138
139
140 def detect(self, input):
141 "Checks if this handler can handle the given data"
142
143 try:
144 self.check(input)
145 return True
146
147 except ( base.FormatError, base.VersionError, base.DataError ):
148 return False
149
150
151 def export_data(self, entrystore, password):
152 "Exports data from an entrystore"
153
154 # set up encryption engine
155 salt = bytes( [ random.choice(string.ascii_lowercase.encode()) for i in range(8) ] )
156 password = MD5.new(salt + password.encode()).digest()
157
158 cipher = Blowfish.new(password, Blowfish.MODE_ECB)
159
160
161 # generate data
162 xml = "<?xml version=\"1.0\" ?>\n"
163 xml += "<FPM full_version=\"00.58.00\" min_version=\"00.58.00\" display_version=\"00.58.00\">\n"
164 xml += " <KeyInfo salt=\"%s\" vstring=\"%s\" />\n" % ( salt.decode(), self.__encrypt(cipher, b"FIGARO").decode() )
165 xml += " <LauncherList></LauncherList>\n"
166 xml += " <PasswordList>\n"
167
168 iter = entrystore.iter_children(None)
169
170 while iter is not None:
171 e = entrystore.get_entry(iter)
172
173 if type(e) != entry.FolderEntry:
174 e = e.convert_generic()
175
176 xml += " <PasswordItem>\n"
177 xml += " <title>%s</title>\n" % self.__encrypt(cipher, e.name.encode()).decode()
178 xml += " <url>%s</url>\n" % self.__encrypt(cipher, e.get_field(entry.HostnameField).value.encode()).decode()
179 xml += " <user>%s</user>\n" % self.__encrypt(cipher, e.get_field(entry.UsernameField).value.encode()).decode()
180 xml += " <password>%s</password>\n" % self.__encrypt(cipher, e.get_field(entry.PasswordField).value.encode()).decode()
181 xml += " <notes>%s</notes>\n" % self.__encrypt(cipher, e.description.encode()).decode()
182
183 path = entrystore.get_path(iter).to_string()
184
185 if len(path) > 1:
186 foldername = entrystore.get_entry(entrystore.get_iter(path[0])).name
187 xml += " <category>%s</category>\n" % self.__encrypt(cipher, foldername.encode()).decode()
188
189 else:
190 xml += " <category></category>\n"
191
192 xml += " <launcher></launcher>\n"
193 xml += " </PasswordItem>\n"
194
195 iter = entrystore.iter_traverse_next(iter)
196
197
198 xml += " </PasswordList>\n"
199 xml += "</FPM>\n"
200
201
202 return xml
203
204
205 def import_data(self, input, password):
206 "Imports data into an entrystore"
207
208 try:
209
210 # check and load data
211 self.check(input)
212 dom = xml.dom.minidom.parseString(input.strip())
213
214 if dom.documentElement.nodeName != "FPM":
215 raise base.FormatError
216
217
218 # set up decryption engine, and check if password is correct
219 keynode = dom.documentElement.getElementsByTagName("KeyInfo")[0]
220 salt = keynode.attributes["salt"].nodeValue.encode()
221 vstring = keynode.attributes["vstring"].nodeValue.encode()
222
223 password = MD5.new(salt + password.encode()).digest()
224 cipher = Blowfish.new(password, Blowfish.MODE_ECB)
225
226 if self.__decrypt(cipher, vstring) != b"FIGARO":
227 raise base.PasswordError
228
229 except ExpatError:
230 raise base.FormatError
231
232
233 except ( IndexError, KeyError ):
234 raise base.FormatError
235
236
237 # import entries into entrystore
238 entrystore = data.EntryStore()
239 folders = {}
240
241 for node in dom.getElementsByTagName("PasswordItem"):
242
243 parent = None
244 e = entry.GenericEntry()
245
246 for fieldnode in [ node for node in node.childNodes if node.nodeType == node.ELEMENT_NODE ]:
247
248 content = self.__decrypt(cipher, util.dom_text(fieldnode)).decode()
249
250 if content == "":
251 continue
252
253 elif fieldnode.nodeName == "title":
254 e.name = content
255
256 elif fieldnode.nodeName == "user":
257 e.get_field(entry.UsernameField).value = content
258
259 elif fieldnode.nodeName == "url":
260 e.get_field(entry.HostnameField).value = content
261
262 elif fieldnode.nodeName == "password":
263 e.get_field(entry.PasswordField).value = content
264
265 elif fieldnode.nodeName == "notes":
266 e.description = content
267
268 elif fieldnode.nodeName == "category":
269
270 if content in folders:
271 parent = folders[content]
272
273 else:
274 folderentry = entry.FolderEntry()
275 folderentry.name = content
276
277 parent = entrystore.add_entry(folderentry)
278 folders[content] = parent
279
280 entrystore.add_entry(e, parent)
281
282 return entrystore
283
284
285