"Fossies" - the Fresh Open Source Software Archive

Member "revelation-0.5.4/src/lib/datahandler/pwsafe.py" (4 Oct 2020, 19227 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 "pwsafe.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 PasswordSafe 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
   28 
   29 import locale, re, struct
   30 from Cryptodome.Cipher import Blowfish
   31 import Cryptodome.Random as Random
   32 
   33 
   34 FIELDTYPE_NAME      = 0x00
   35 FIELDTYPE_UUID      = 0x01
   36 FIELDTYPE_GROUP     = 0x02
   37 FIELDTYPE_TITLE     = 0x03
   38 FIELDTYPE_USER      = 0x04
   39 FIELDTYPE_NOTES     = 0x05
   40 FIELDTYPE_PASSWORD  = 0x06
   41 FIELDTYPE_END       = 0xff
   42 
   43 
   44 
   45 # We need our own SHA1-implementation, because Password Safe does
   46 # non-standard things we need to replicate. This implementation is
   47 # written by J. Hallen and L. Creighton for the Pypy project, with
   48 # slight modifications by Erik Grinaker.
   49 class SHA:
   50 
   51     K = [
   52         0x5A827999,
   53         0x6ED9EBA1,
   54         0x8F1BBCDC,
   55         0xCA62C1D6
   56     ]
   57 
   58 
   59     def __init__(self, input = None):
   60         self.count = [0, 0]
   61         self.init()
   62 
   63         if input != None:
   64             self.update(input)
   65 
   66 
   67     def __bytelist2longBigEndian(self, list):
   68         imax = len(list)//4
   69         hl = [0] * imax
   70 
   71         j = 0
   72         i = 0
   73         while i < imax:
   74             b0 = list[j] << 24
   75             b1 = list[j+1] << 16
   76             b2 = list[j+2] << 8
   77             b3 = list[j+3]
   78             hl[i] = b0 | b1 | b2 | b3
   79             i = i+1
   80             j = j+4
   81 
   82         return hl
   83 
   84 
   85     def __long2bytesBigEndian(self, n, blocksize=0):
   86         s = b''
   87         pack = struct.pack
   88         while n > 0:
   89             s = pack('>I', n & 0xffffffff) + s
   90             n = n >> 32
   91 
   92         for i in range(len(s)):
   93             if s[i] != b'\000':
   94                 break
   95         else:
   96             s = b'\000'
   97             i = 0
   98 
   99         s = s[i:]
  100 
  101         if blocksize > 0 and len(s) % blocksize:
  102             s = (blocksize - len(s) % blocksize) * b'\000' + s
  103 
  104         return s
  105 
  106 
  107     def __rotateLeft(self, x, n):
  108         return (x << n) | (x >> (32-n))
  109 
  110 
  111     def __transform(self, W):
  112         for t in range(16, 80):
  113             W.append(self.__rotateLeft(
  114                 W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1) & 0xffffffff)
  115 
  116         A = self.H0
  117         B = self.H1
  118         C = self.H2
  119         D = self.H3
  120         E = self.H4
  121 
  122         for t in range(0, 20):
  123             TEMP = self.__rotateLeft(A, 5) + ((B & C) | ((~ B) & D)) + E + W[t] + self.K[0]
  124             E = D
  125             D = C
  126             C = self.__rotateLeft(B, 30) & 0xffffffff
  127             B = A
  128             A = TEMP & 0xffffffff
  129 
  130         for t in range(20, 40):
  131             TEMP = self.__rotateLeft(A, 5) + (B ^ C ^ D) + E + W[t] + self.K[1]
  132             E = D
  133             D = C
  134             C = self.__rotateLeft(B, 30) & 0xffffffff
  135             B = A
  136             A = TEMP & 0xffffffff
  137 
  138         for t in range(40, 60):
  139             TEMP = self.__rotateLeft(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[t] + self.K[2]
  140             E = D
  141             D = C
  142             C = self.__rotateLeft(B, 30) & 0xffffffff
  143             B = A
  144             A = TEMP & 0xffffffff
  145 
  146         for t in range(60, 80):
  147             TEMP = self.__rotateLeft(A, 5) + (B ^ C ^ D)  + E + W[t] + self.K[3]
  148             E = D
  149             D = C
  150             C = self.__rotateLeft(B, 30) & 0xffffffff
  151             B = A
  152             A = TEMP & 0xffffffff
  153 
  154 
  155         self.H0 = (self.H0 + A) & 0xffffffff
  156         self.H1 = (self.H1 + B) & 0xffffffff
  157         self.H2 = (self.H2 + C) & 0xffffffff
  158         self.H3 = (self.H3 + D) & 0xffffffff
  159         self.H4 = (self.H4 + E) & 0xffffffff
  160 
  161 
  162     def digest(self):
  163         H0 = self.H0
  164         H1 = self.H1
  165         H2 = self.H2
  166         H3 = self.H3
  167         H4 = self.H4
  168         input = [] + self.input
  169         count = [] + self.count
  170 
  171         index = (self.count[1] >> 3) & 0x3f
  172 
  173         if index < 56:
  174             padLen = 56 - index
  175         else:
  176             padLen = 120 - index
  177 
  178         padding = [128] + [0] * 63
  179         self.update(padding[:padLen])
  180         bits = self.__bytelist2longBigEndian(self.input[:56]) + count
  181 
  182         self.__transform(bits)
  183 
  184         digest = self.__long2bytesBigEndian(self.H0, 4) + \
  185                 self.__long2bytesBigEndian(self.H1, 4) + \
  186                 self.__long2bytesBigEndian(self.H2, 4) + \
  187                 self.__long2bytesBigEndian(self.H3, 4) + \
  188                 self.__long2bytesBigEndian(self.H4, 4)
  189 
  190         self.H0 = H0
  191         self.H1 = H1
  192         self.H2 = H2
  193         self.H3 = H3
  194         self.H4 = H4
  195         self.input = input
  196         self.count = count
  197 
  198         return digest
  199 
  200 
  201     def hexdigest(self):
  202         return ''.join(['%02x' % ord(c) for c in self.digest()])
  203 
  204 
  205     def init(self, H0 = 0x67452301, H1 = 0xEFCDAB89, H2 = 0x98BADCFE, H3 = 0x10325476, H4 = 0xC3D2E1F0):
  206         self.length = 0
  207         self.input = []
  208 
  209         self.H0 = H0
  210         self.H1 = H1
  211         self.H2 = H2
  212         self.H3 = H3
  213         self.H4 = H4
  214 
  215 
  216     def update(self, inBuf):
  217         leninBuf = len(inBuf)
  218 
  219         index = (self.count[1] >> 3) & 0x3F
  220 
  221         self.count[1] = self.count[1] + (leninBuf << 3)
  222         if self.count[1] < (leninBuf << 3):
  223             self.count[0] = self.count[0] + 1
  224         self.count[0] = self.count[0] + (leninBuf >> 29)
  225 
  226         partLen = 64 - index
  227 
  228         if leninBuf >= partLen:
  229             self.input[index:] = list(inBuf[:partLen])
  230             self.__transform(self.__bytelist2longBigEndian(self.input))
  231             i = partLen
  232             while i + 63 < leninBuf:
  233                 self.__transform(self.__bytelist2longBigEndian(list(inBuf[i:i+64])))
  234                 i = i + 64
  235             else:
  236                 self.input = list(inBuf[i:leninBuf])
  237         else:
  238             i = 0
  239             self.input = self.input + list(inBuf)
  240 
  241 
  242 # misc functions common to both datahandlers
  243 def decrypt(key, ciphertext, iv = None):
  244     "Decrypts data"
  245 
  246     if len(ciphertext) % 8 != 0:
  247         raise base.FormatError
  248 
  249     cipher      = Blowfish.new(key, Blowfish.MODE_ECB)
  250     cbc     = iv
  251     plaintext   = b""
  252 
  253     for cipherblock in [ ciphertext[i * 8 : (i + 1) * 8] for i in range(len(ciphertext) // 8) ]:
  254 
  255         plainblock = decrypt_block(cipher, cipherblock)
  256 
  257         if cbc != None:
  258             plainblock = bytes([ plainblock[i] ^ cbc[i] for i in range(len(plainblock)) ])
  259             cbc = cipherblock
  260 
  261         plaintext += plainblock
  262 
  263     return plaintext
  264 
  265 
  266 def decrypt_block(cipher, block):
  267     "Decrypts a block with the given cipher"
  268 
  269     block = bytes((block[3], block[2], block[1], block[0], block[7], block[6], block[5], block[4]))
  270     block = cipher.decrypt(block)
  271     block = bytes((block[3], block[2], block[1], block[0], block[7], block[6], block[5], block[4]))
  272 
  273     return block
  274 
  275 
  276 def encrypt(key, plaintext, iv = None):
  277     "Encrypts data"
  278 
  279     if len(plaintext) % 8 != 0:
  280         raise base.FormatError
  281 
  282     cipher      = Blowfish.new(key, Blowfish.MODE_ECB)
  283     cbc     = iv
  284     ciphertext  = b""
  285 
  286     for plainblock in [ plaintext[i * 8 : (i + 1) * 8] for i in range(len(plaintext) // 8) ]:
  287 
  288         if cbc != None:
  289             plainblock = bytes([ plainblock[i] ^ cbc[i] for i in range(len(plainblock)) ])
  290 
  291         cipherblock = encrypt_block(cipher, plainblock)
  292         ciphertext += cipherblock
  293 
  294         if cbc != None:
  295             cbc = cipherblock
  296 
  297 
  298     return ciphertext
  299 
  300 
  301 def encrypt_block(cipher, block):
  302     "Encrypts a block with the given cipher"
  303 
  304     block = bytes((block[3], block[2], block[1], block[0], block[7], block[6], block[5], block[4]))
  305     block = cipher.encrypt(block)
  306     block = bytes((block[3], block[2], block[1], block[0], block[7], block[6], block[5], block[4]))
  307 
  308     return block
  309 
  310 
  311 def generate_testhash(password, random):
  312     "Generates a testhash based on a password and a random string"
  313 
  314     key = SHA(random + b"\x00\x00" + password.encode()).digest()
  315     cipher  = Blowfish.new(key, Blowfish.MODE_ECB)
  316 
  317     for i in range(1000):
  318         random = encrypt_block(cipher, random)
  319 
  320     h = SHA()
  321     h.init(0, 0, 0, 0, 0)
  322     h.update(random)
  323     h.update(b"\x00\x00")
  324     testhash = h.digest()
  325 
  326     return testhash
  327 
  328 
  329 
  330 def create_field(value, type = FIELDTYPE_NAME):
  331     "Creates a field"
  332     if isinstance(value, str):
  333         value = value.encode()
  334 
  335     field = struct.pack("ii", len(value), type) + value
  336 
  337     if len(value) == 0 or len(value) % 8 != 0:
  338         field += b"\x00" * (8 - len(value) % 8)
  339 
  340     return field
  341 
  342 
  343 def normalize_field(field):
  344     "Normalizes a field value"
  345 
  346     enc = locale.getpreferredencoding()
  347 
  348     field = field.replace(b"\x00", b"")
  349     field = re.sub(b"\s+", b" ", field)
  350     field = field.strip()
  351     field = field.decode(enc, "replace")
  352 
  353     return field
  354 
  355 
  356 def parse_field_header(header):
  357     "Parses field data, returns the length and type"
  358 
  359     if len(header) < 8:
  360         raise base.FormatError
  361 
  362     length, type = struct.unpack("ii", header[:8])
  363 
  364     if length == 0 or length % 8 != 0:
  365         length += 8 - length % 8
  366 
  367     return length, type
  368 
  369 
  370 
  371 class PasswordSafe1(base.DataHandler):
  372     "Data handler for PasswordSafe 1.x data"
  373 
  374     name        = "Password Safe 1.x"
  375     importer    = True
  376     exporter    = True
  377     encryption  = True
  378 
  379 
  380     def __init__(self):
  381         base.DataHandler.__init__(self)
  382 
  383 
  384     def check(self, input):
  385         "Checks if the data is valid"
  386 
  387         if input is None:
  388             raise base.FormatError
  389 
  390         if len(input) < 56:
  391             raise base.FormatError
  392 
  393         if (len(input) - 56) % 8 != 0:
  394             raise base.FormatError
  395 
  396 
  397     def export_data(self, entrystore, password):
  398         "Exports data from an entrystore"
  399 
  400         # serialize data
  401         enc = locale.getpreferredencoding()
  402         db = b""
  403         iter = entrystore.iter_children(None)
  404 
  405         while iter is not None:
  406             e = entrystore.get_entry(iter)
  407 
  408             if type(e) != entry.FolderEntry:
  409                 e = e.convert_generic()
  410 
  411                 edata = b""
  412                 edata += create_field(e.name.encode(enc, "replace") + b"\xAD" + e[entry.UsernameField].encode("iso-8859-1"))
  413                 edata += create_field(e[entry.PasswordField].encode(enc, "replace"))
  414                 edata += create_field(e.description.encode(enc, "replace"))
  415 
  416                 db += edata
  417 
  418             iter = entrystore.iter_traverse_next(iter)
  419 
  420 
  421         # encrypt data
  422         rand = Random.new()
  423         random = rand.read(8)
  424         salt   = rand.read(20)
  425         iv     = rand.read(8)
  426 
  427         testhash    = generate_testhash(password, random)
  428         ciphertext  = encrypt(SHA(password.encode() + salt).digest(), db, iv)
  429 
  430         return random + testhash + salt + iv + ciphertext
  431 
  432 
  433     def import_data(self, input, password):
  434         "Imports data into an entrystore"
  435 
  436         # read header and test password
  437         if password is None:
  438             raise base.PasswordError
  439 
  440         random      = input[0:8]
  441         testhash    = input[8:28]
  442         salt        = input[28:48]
  443         iv      = input[48:56]
  444 
  445         if testhash != generate_testhash(password, random):
  446             raise base.PasswordError
  447 
  448         # load data
  449         db      = decrypt(SHA(password.encode() + salt).digest(), input[56:], iv)
  450         entrystore  = data.EntryStore()
  451 
  452         while len(db) > 0:
  453 
  454             dbentry = { "name" : "", "username" : "", "password" : "", "note" : "" }
  455 
  456             for f in ( "name", "password", "note" ):
  457                 flen, ftype = parse_field_header(db[:8])
  458                 value = db[8:8 + flen]
  459 
  460                 if f == "name" and b"\xAD" in value:
  461                     value, dbentry["username"] = value.split(b"\xAD", 1)
  462 
  463                 dbentry[f] = value
  464                 db = db[8 + flen:]
  465 
  466             e = entry.GenericEntry()
  467             e.name          = normalize_field(dbentry["name"])
  468             e.description       = normalize_field(dbentry["note"])
  469             e[entry.UsernameField]  = normalize_field(dbentry["username"])
  470             e[entry.PasswordField]  = normalize_field(dbentry["password"])
  471 
  472             entrystore.add_entry(e)
  473 
  474         return entrystore
  475 
  476 
  477 
  478 class PasswordSafe2(base.DataHandler):
  479     "Data handler for PasswordSafe 2.x data"
  480 
  481     name        = "Password Safe 2.x"
  482     importer    = True
  483     exporter    = True
  484     encryption  = True
  485 
  486 
  487     def __init__(self):
  488         base.DataHandler.__init__(self)
  489 
  490 
  491     def __get_group(self, entrystore, iter):
  492         "Returns the group path for an iter"
  493 
  494         path = []
  495 
  496         iter = entrystore.iter_parent(iter)
  497 
  498         while iter is not None:
  499             path.append(entrystore.get_entry(iter).name)
  500             iter = entrystore.iter_parent(iter)
  501 
  502         path.reverse()
  503 
  504         return ".".join(path)
  505 
  506 
  507     def __setup_group(self, entrystore, groupmap, group):
  508         "Sets up a group folder, or returns an existing one"
  509 
  510         if group in ( None, "" ):
  511             return None
  512 
  513         if group in groupmap:
  514             return groupmap[group]
  515 
  516         if "." in group:
  517             parent, groupname = group.rsplit(".", 1)
  518             parentiter = self.__setup_group(entrystore, groupmap, parent)
  519 
  520         else:
  521             groupname = group
  522             parentiter = None
  523 
  524         e = entry.FolderEntry()
  525         e.name = groupname
  526 
  527         iter = entrystore.add_entry(e, parentiter)
  528         groupmap[group] = iter
  529 
  530         return iter
  531 
  532 
  533     def check(self, input):
  534         "Checks if the data is valid"
  535 
  536         if input is None:
  537             raise base.FormatError
  538 
  539         if len(input) < 56:
  540             raise base.FormatError
  541 
  542         if (len(input) - 56) % 8 != 0:
  543             raise base.FormatError
  544 
  545 
  546     def export_data(self, entrystore, password):
  547         "Exports data from an entrystore"
  548 
  549         # set up magic entry at start of database
  550         db = b""
  551         db += b"\x48\x00\x00\x00\x00\x00\x00\x00"
  552         db += b" !!!Version 2 File Format!!! Please upgrade to PasswordSafe 2.0 or later"
  553         db += b"\x03\x00\x00\x00\x06\x00\x00\x00"
  554         db += b"2.0\x00\x00\x00\x00\x00"
  555         db += b"\x00\x00\x00\x00\x06\x00\x00\x00"
  556         db += b"\x00\x00\x00\x00\x00\x00\x00\x00"
  557 
  558         # serialize data
  559         uuids = []
  560         iter = entrystore.iter_children(None)
  561 
  562         enc = locale.getpreferredencoding()
  563         rand = Random.new()
  564 
  565         while iter is not None:
  566             e = entrystore.get_entry(iter)
  567 
  568             if type(e) != entry.FolderEntry:
  569                 e = e.convert_generic()
  570 
  571                 uuid = rand.read(16)
  572 
  573                 while uuid in uuids:
  574                     uuid = rand.read(16)
  575 
  576                 edata = b""
  577                 edata += create_field(uuid, FIELDTYPE_UUID)
  578                 edata += create_field(self.__get_group(entrystore, iter), FIELDTYPE_GROUP)
  579                 edata += create_field(e.name.encode(enc, "replace"), FIELDTYPE_TITLE)
  580                 s = e[entry.UsernameField]
  581                 if s is None:
  582                     s = ""
  583                 edata += create_field(s.encode(enc, "replace"), FIELDTYPE_USER)
  584                 edata += create_field(e[entry.PasswordField].encode(enc, "replace"), FIELDTYPE_PASSWORD)
  585                 edata += create_field(e.description.encode(enc, "replace"), FIELDTYPE_NOTES)
  586                 edata += create_field("", FIELDTYPE_END)
  587 
  588                 db += edata
  589 
  590             iter = entrystore.iter_traverse_next(iter)
  591 
  592 
  593         # encrypt data
  594         rand = Random.new()
  595         random  = rand.read(8)
  596         salt    = rand.read(20)
  597         iv      = rand.read(8)
  598 
  599         testhash    = generate_testhash(password, random)
  600         ciphertext  = encrypt(SHA(password.encode() + salt).digest(), db, iv)
  601 
  602         return random + testhash + salt + iv + ciphertext
  603 
  604 
  605     def import_data(self, input, password):
  606         "Imports data into an entrystore"
  607 
  608         # read header and test password
  609         if password is None:
  610             raise base.PasswordError
  611 
  612         random      = input[0:8]
  613         testhash    = input[8:28]
  614         salt        = input[28:48]
  615         iv      = input[48:56]
  616 
  617         if testhash != generate_testhash(password, random):
  618             raise base.PasswordError
  619 
  620         # load data
  621         db      = decrypt(SHA(password.encode() + salt).digest(), input[56:], iv)
  622         entrystore  = data.EntryStore()
  623 
  624 
  625         # read magic entry
  626         for f in "magic", "version", "prefs":
  627             flen, ftype = parse_field_header(db)
  628             value = db[8:8 + flen]
  629 
  630             if f == "magic" and value != b" !!!Version 2 File Format!!! Please upgrade to PasswordSafe 2.0 or later":
  631                 raise base.FormatError
  632 
  633             db = db[8 + flen:]
  634 
  635         # import entries
  636         e = entry.GenericEntry()
  637         group = None
  638         groupmap = {}
  639 
  640         while len(db) > 0:
  641             flen, ftype = parse_field_header(db)
  642             value = normalize_field(db[8:8 + flen])
  643 
  644             if ftype == FIELDTYPE_NAME:
  645                 if b"\xAD" not in value:
  646                     e.name = value
  647 
  648                 else:
  649                     n, u = value.split(b"\xAD", 1)
  650 
  651                     e.name = normalize_field(n)
  652                     e[entry.UsernameField] = normalize_field(u)
  653 
  654             elif ftype == FIELDTYPE_TITLE:
  655                 e.name = value
  656 
  657             elif ftype == FIELDTYPE_USER:
  658                 e[entry.UsernameField] = value
  659 
  660             elif ftype == FIELDTYPE_PASSWORD:
  661                 e[entry.PasswordField] = value
  662 
  663             elif ftype == FIELDTYPE_NOTES:
  664                 e.description = value
  665 
  666             elif ftype == FIELDTYPE_UUID:
  667                 pass
  668 
  669             elif ftype == FIELDTYPE_GROUP:
  670                 group = value
  671 
  672             elif ftype == FIELDTYPE_END:
  673                 if group not in ( None, "" ):
  674                     parent = self.__setup_group(entrystore, groupmap, group)
  675 
  676                 else:
  677                     parent = None
  678 
  679                 entrystore.add_entry(e, parent)
  680 
  681                 e = entry.GenericEntry()
  682                 group = None
  683 
  684             else:
  685                 pass
  686 
  687             db = db[8 + flen:]
  688 
  689         return entrystore
  690 
  691 
  692 
  693 class MyPasswordSafe(PasswordSafe2):
  694     "Data handler for MyPasswordSafe data"
  695 
  696     name        = "MyPasswordSafe"
  697     importer    = True
  698     exporter    = True
  699     encryption  = True
  700 
  701 
  702 
  703 class MyPasswordSafeOld(PasswordSafe1):
  704     "Data handler for old MyPasswordSafe data"
  705 
  706     name        = "MyPasswordSafe (old format)"
  707     importer    = True
  708     exporter    = True
  709     encryption  = True
  710 
  711 
  712 
  713 class PasswordGorilla(PasswordSafe2):
  714     "Data handler for Password Gorilla data"
  715 
  716     name        = "Password Gorilla"
  717     importer    = True
  718     exporter    = True
  719     encryption  = True
  720