"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