fpm.py (revelation-0.5.3.tar.xz) | : | fpm.py (revelation-0.5.4.tar.xz) | ||
---|---|---|---|---|
skipping to change at line 34 | skipping to change at line 34 | |||
# | # | |||
from . import base | from . import base | |||
from revelation import data, entry, util | from revelation import data, entry, util | |||
import math, random, string, xml.dom.minidom | import math, random, string, xml.dom.minidom | |||
from xml.parsers.expat import ExpatError | from xml.parsers.expat import ExpatError | |||
from Cryptodome.Cipher import Blowfish | from Cryptodome.Cipher import Blowfish | |||
from Cryptodome.Hash import MD5 | from Cryptodome.Hash import MD5 | |||
import Cryptodome.Random as Random | ||||
class FPM(base.DataHandler): | class FPM(base.DataHandler): | |||
"Data handler for Figaro's Password Manager data" | "Data handler for Figaro's Password Manager data" | |||
name = "Figaro's Password Manager" | name = "Figaro's Password Manager" | |||
importer = True | importer = True | |||
exporter = True | exporter = True | |||
encryption = True | encryption = True | |||
def __init__(self): | def __init__(self): | |||
base.DataHandler.__init__(self) | base.DataHandler.__init__(self) | |||
def __decrypt(self, cipher, data): | def __decrypt(self, cipher, data): | |||
"Decrypts data" | "Decrypts data" | |||
# decode ascii armoring | if isinstance(data, str): | |||
decoded = "" | data = data.encode() | |||
for i in range(len(data) / 2): | # decode ascii armoring | |||
high = ord(data[2 * i]) - ord("a") | decoded = b"" | |||
low = ord(data[2 * i + 1]) - ord("a") | ||||
decoded += chr(high * 16 + low) | ||||
for i in range(len(data) // 2): | ||||
high = data[2 * i] - ord("a") | ||||
low = data[2 * i + 1] - ord("a") | ||||
decoded += bytes((high * 16 + low,)) | ||||
data = decoded | data = decoded | |||
# decrypt data | # decrypt data | |||
data = cipher.decrypt(data) | data = cipher.decrypt(data) | |||
# unrotate field | # unrotate field | |||
blocks = int(math.ceil(len(data) / float(8))) | blocks = int(math.ceil(len(data) / float(8))) | |||
plain = "" | plain = b"" | |||
for offset in range(8): | for offset in range(8): | |||
for block in range(blocks): | for block in range(blocks): | |||
plain += data[block * 8 + offset] | plain += bytes((data[block * 8 + offset],)) | |||
return plain.split("\x00")[0] | return plain.split(b"\x00")[0] | |||
def __encrypt(self, cipher, data): | def __encrypt(self, cipher, data): | |||
"Encrypts data" | "Encrypts data" | |||
# get data sizes | # get data sizes | |||
blocks = (len(data) / 7) + 1 | blocks = (len(data) // 7) + 1 | |||
size = 8 * blocks | size = 8 * blocks | |||
# add noise | # add noise | |||
data += "\x00" + util.random_string(size - len(data) - 1) | rand = Random.new() | |||
data += b'\x00' + rand.read(size - len(data) - 1) | ||||
# rotate data | # rotate data | |||
rotated = "" | rotated = b"" | |||
for block in range(blocks): | for block in range(blocks): | |||
for offset in range(8): | for offset in range(8): | |||
rotated += data[offset * blocks + block] | rotated += bytes((data[offset * blocks + block],)) | |||
data = rotated | data = rotated | |||
# encrypt data | # encrypt data | |||
data = cipher.encrypt(data) | data = cipher.encrypt(data) | |||
# ascii-armor data | # ascii-armor data | |||
res = "" | res = b"" | |||
for i in range(len(data)): | for i in range(len(data)): | |||
high = ord(data[i]) / 16 | high = data[i] // 16 | |||
low = ord(data[i]) - high * 16 | low = data[i] - high * 16 | |||
res += chr(ord("a") + high) + chr(ord("a") + low) | res += bytes((ord("a") + high, ord("a") + low)) | |||
data = res | data = res | |||
return data | return data | |||
def check(self, input): | def check(self, input): | |||
"Checks if the data is valid" | "Checks if the data is valid" | |||
try: | try: | |||
if input is None: | if input is None: | |||
skipping to change at line 142 | skipping to change at line 146 | |||
self.check(input) | self.check(input) | |||
return True | return True | |||
except ( base.FormatError, base.VersionError, base.DataError ): | except ( base.FormatError, base.VersionError, base.DataError ): | |||
return False | return False | |||
def export_data(self, entrystore, password): | def export_data(self, entrystore, password): | |||
"Exports data from an entrystore" | "Exports data from an entrystore" | |||
# set up encryption engine | # set up encryption engine | |||
salt = "".join( [ random.choice(string.ascii_lowercase) for i in range(8 | salt = bytes( [ random.choice(string.ascii_lowercase.encode()) for i in | |||
) ] ) | range(8) ] ) | |||
password = MD5.new(salt + password).digest() | password = MD5.new(salt + password.encode()).digest() | |||
cipher = Blowfish.new(password) | cipher = Blowfish.new(password, Blowfish.MODE_ECB) | |||
# generate data | # generate data | |||
xml = "<?xml version=\"1.0\" ?>\n" | xml = "<?xml version=\"1.0\" ?>\n" | |||
xml += "<FPM full_version=\"00.58.00\" min_version=\"00.58.00\" display_ version=\"00.58.00\">\n" | xml += "<FPM full_version=\"00.58.00\" min_version=\"00.58.00\" display_ version=\"00.58.00\">\n" | |||
xml += " <KeyInfo salt=\"%s\" vstring=\"%s\" />\n" % ( salt, self.__e ncrypt(cipher, "FIGARO") ) | xml += " <KeyInfo salt=\"%s\" vstring=\"%s\" />\n" % ( salt.decode(), self.__encrypt(cipher, b"FIGARO").decode() ) | |||
xml += " <LauncherList></LauncherList>\n" | xml += " <LauncherList></LauncherList>\n" | |||
xml += " <PasswordList>\n" | xml += " <PasswordList>\n" | |||
iter = entrystore.iter_children(None) | iter = entrystore.iter_children(None) | |||
while iter is not None: | while iter is not None: | |||
e = entrystore.get_entry(iter) | e = entrystore.get_entry(iter) | |||
if type(e) != entry.FolderEntry: | if type(e) != entry.FolderEntry: | |||
e = e.convert_generic() | e = e.convert_generic() | |||
xml += " <PasswordItem>\n" | xml += " <PasswordItem>\n" | |||
xml += " <title>%s</title>\n" % e.name | xml += " <title>%s</title>\n" % self.__encrypt(cipher | |||
xml += " <url>%s</url>\n" % e.get_field(entry.Hostnam | , e.name.encode()).decode() | |||
eField).value | xml += " <url>%s</url>\n" % self.__encrypt(cipher, e. | |||
xml += " <user>%s</user>\n" % e.get_field(entry.Usern | get_field(entry.HostnameField).value.encode()).decode() | |||
ameField).value | xml += " <user>%s</user>\n" % self.__encrypt(cipher, | |||
xml += " <password>%s</password>\n" % e.get_field(ent | e.get_field(entry.UsernameField).value.encode()).decode() | |||
ry.PasswordField).value | xml += " <password>%s</password>\n" % self.__encrypt( | |||
xml += " <notes>%s</notes>\n" % e.description | cipher, e.get_field(entry.PasswordField).value.encode()).decode() | |||
xml += " <notes>%s</notes>\n" % self.__encrypt(cipher | ||||
, e.description.encode()).decode() | ||||
path = entrystore.get_path(iter) | path = entrystore.get_path(iter).to_string() | |||
if len(path) > 1: | if len(path) > 1: | |||
xml += " <category>%s</category>\n" % entrystore. | foldername = entrystore.get_entry(entrystore.get_iter(path[0 | |||
get_entry(entrystore.get_iter(path[0])).name | ])).name | |||
xml += " <category>%s</category>\n" % self.__encr | ||||
ypt(cipher, foldername.encode()).decode() | ||||
else: | else: | |||
xml += " <category></category>\n" | xml += " <category></category>\n" | |||
xml += " <launcher></launcher>\n" | xml += " <launcher></launcher>\n" | |||
xml += " </PasswordItem>\n" | xml += " </PasswordItem>\n" | |||
iter = entrystore.iter_traverse_next(iter) | iter = entrystore.iter_traverse_next(iter) | |||
xml += " </PasswordList>\n" | xml += " </PasswordList>\n" | |||
skipping to change at line 201 | skipping to change at line 206 | |||
# check and load data | # check and load data | |||
self.check(input) | self.check(input) | |||
dom = xml.dom.minidom.parseString(input.strip()) | dom = xml.dom.minidom.parseString(input.strip()) | |||
if dom.documentElement.nodeName != "FPM": | if dom.documentElement.nodeName != "FPM": | |||
raise base.FormatError | raise base.FormatError | |||
# set up decryption engine, and check if password is correct | # set up decryption engine, and check if password is correct | |||
keynode = dom.documentElement.getElementsByTagName("KeyInfo")[0] | keynode = dom.documentElement.getElementsByTagName("KeyInfo")[0] | |||
salt = keynode.attributes["salt"].nodeValue | salt = keynode.attributes["salt"].nodeValue.encode() | |||
vstring = keynode.attributes["vstring"].nodeValue | vstring = keynode.attributes["vstring"].nodeValue.encode() | |||
password = MD5.new(salt + password).digest() | password = MD5.new(salt + password.encode()).digest() | |||
cipher = Blowfish.new(password) | cipher = Blowfish.new(password, Blowfish.MODE_ECB) | |||
if self.__decrypt(cipher, vstring) != "FIGARO": | if self.__decrypt(cipher, vstring) != b"FIGARO": | |||
raise base.PasswordError | raise base.PasswordError | |||
except ExpatError: | except ExpatError: | |||
raise base.FormatError | raise base.FormatError | |||
except ( IndexError, KeyError ): | except ( IndexError, KeyError ): | |||
raise base.FormatError | raise base.FormatError | |||
# import entries into entrystore | # import entries into entrystore | |||
entrystore = data.EntryStore() | entrystore = data.EntryStore() | |||
folders = {} | folders = {} | |||
for node in dom.getElementsByTagName("PasswordItem"): | for node in dom.getElementsByTagName("PasswordItem"): | |||
parent = None | parent = None | |||
e = entry.GenericEntry() | e = entry.GenericEntry() | |||
for fieldnode in [ node for node in node.childNodes if node.nodeType == node.ELEMENT_NODE ]: | for fieldnode in [ node for node in node.childNodes if node.nodeType == node.ELEMENT_NODE ]: | |||
content = self.__decrypt(cipher, util.dom_text(fieldnode)) | content = self.__decrypt(cipher, util.dom_text(fieldnode)).decod e() | |||
if content == "": | if content == "": | |||
continue | continue | |||
elif fieldnode.nodeName == "title": | elif fieldnode.nodeName == "title": | |||
e.name = content | e.name = content | |||
elif fieldnode.nodeName == "user": | elif fieldnode.nodeName == "user": | |||
e.get_field(entry.UsernameField).value = content | e.get_field(entry.UsernameField).value = content | |||
End of changes. 23 change blocks. | ||||
39 lines changed or deleted | 47 lines changed or added |