Example #1
0
    def __init__(self, filepath=None, password=None, keyfile=None, 
                 read_only=False, new = False):
        """ Initialize a new or an existing database.

        If a 'filepath' and a 'masterkey' is passed 'load' will try to open
        a database. If 'True' is passed to 'read_only' the database will open
        read-only. It's also possible to create a new one, just pass 'True' to
        new. This will be ignored if a filepath and a masterkey is given this
        will be ignored.
        
        """
        
        
     
        if filepath is not None and password is None and keyfile is None:
            raise KPError('Missing argument: Password or keyfile '
                          'needed additionally to open an existing database!')
        elif type(read_only) is not bool or type(new) is not bool:
            raise KPError('read_only and new must be bool')
        elif ((filepath is not None and type(filepath) is not str) or
              (type(password) is not str and password is not None) or
              (type(keyfile) is not str and keyfile is not None)):
            raise KPError('filepath, masterkey and keyfile must be a string')
        elif (filepath is None and password is None and keyfile is None and 
              new is False):
            raise KPError('Either an existing database should be opened or '
                          'a new should be created.')

        self.groups = []
        self.entries = []
        self.root_group = v1Group()
        self.read_only = read_only
        self.filepath = filepath
        self.password = password
        self.keyfile = keyfile

        # This are attributes that are needed internally. You should not
        # change them directly, it could damage the database!
        self._group_order = []
        self._entry_order = []
        self._signature1 = 0x9AA2D903
        self._signature2 = 0xB54BFB65
        self._enc_flag = 2
        self._version = 0x00030002
        self._final_randomseed = ''
        self._enc_iv = ''
        self._num_groups = 1
        self._num_entries = 0
        self._contents_hash = ''
        Random.atfork()
        self._transf_randomseed = Random.get_random_bytes(32)
        self._key_transf_rounds = 150000

        # Due to the design of KeePass, at least one group is needed.
        if new is True:
            self._group_order = [("id", 1), (1, 4), (2, 9), (7, 4), (8, 2),
                                 (0xFFFF, 0)]
            group = v1Group(1, 'Internet', 1, self, parent = self.root_group)
            self.root_group.children.append(group)
            self.groups.append(group)
Example #2
0
    def __init__(self, filepath=None, password=None, keyfile=None, 
                 read_only=False, new = False):
        """ Initialize a new or an existing database.

        If a 'filepath' and a 'masterkey' is passed 'load' will try to open
        a database. If 'True' is passed to 'read_only' the database will open
        read-only. It's also possible to create a new one, just pass 'True' to
        new. This will be ignored if a filepath and a masterkey is given this
        will be ignored.
        
        """

        if filepath is not None and password is None and keyfile is None:
            raise KPError('Missing argument: Password or keyfile '
                          'needed additionally to open an existing database!')
        elif type(read_only) is not bool or type(new) is not bool:
            raise KPError('read_only and new must be bool')
        elif ((filepath is not None and type(filepath) is not str) or
              (type(password) is not str and password is not None) or
              (type(keyfile) is not str and keyfile is not None)):
            raise KPError('filepath, masterkey and keyfile must be a string')
        elif (filepath is None and password is None and keyfile is None and 
              new is False):
            raise KPError('Either an existing database should be opened or '
                          'a new should be created.')

        self.groups = []
        self.entries = []
        self.root_group = v1Group()
        self.read_only = read_only
        self.filepath = filepath
        self.password = password
        self.keyfile = keyfile

        # This are attributes that are needed internally. You should not
        # change them directly, it could damage the database!
        self._group_order = []
        self._entry_order = []
        self._signature1 = 0x9AA2D903
        self._signature2 = 0xB54BFB65
        self._enc_flag = 2
        self._version = 0x00030002
        self._final_randomseed = ''
        self._enc_iv = ''
        self._num_groups = 1
        self._num_entries = 0
        self._contents_hash = ''
        Random.atfork()
        self._transf_randomseed = Random.get_random_bytes(32)
        self._key_transf_rounds = 150000

        # Due to the design of KeePass, at least one group is needed.
        if new is True:
            self._group_order = [("id", 1), (1, 4), (2, 9), (7, 4), (8, 2),
                                 (0xFFFF, 0)]
            group = v1Group(1, 'Internet', 1, self, parent = self.root_group)
            self.root_group.children.append(group)
            self.groups.append(group)
Example #3
0
    def create_group(self,
                     title=None,
                     parent=None,
                     image=1,
                     y=2999,
                     mon=12,
                     d=28,
                     h=23,
                     min_=59,
                     s=59):
        """This method creates a new group.

        A group title is needed or no group will be created.

        If a parent is given, the group will be created as a sub-group.

        title must be a string, image an unsigned int >0 and parent a v1Group.

        With y, mon, d, h, min_ and s you can set an expiration date like on
        entries.

        """

        if title is None:
            raise KPError("Need a group title to create a group.")
        elif type(title) is not str or image < 1 or(parent is not None and \
            type(parent) is not v1Group) or type(image) is not int:
            raise KPError("Wrong type or value for title or image or parent")

        id_ = 1
        for i in self.groups:
            if i.id_ >= id_:
                id_ = i.id_ + 1
        group = v1Group(id_, title, image, self)
        group.creation = datetime.now().replace(microsecond=0)
        group.last_mod = datetime.now().replace(microsecond=0)
        group.last_access = datetime.now().replace(microsecond=0)
        if group.set_expire(y, mon, d, h, min_, s) is False:
            group.set_expire()

        # If no parent is given, just append the new group at the end
        if parent is None:
            group.parent = self.root_group
            self.root_group.children.append(group)
            group.level = 0
            self.groups.append(group)
        # Else insert the group behind the parent
        else:
            if parent in self.groups:
                parent.children.append(group)
                group.parent = parent
                group.level = parent.level + 1
                self.groups.insert(self.groups.index(parent) + 1, group)
            else:
                raise KPError("Given parent doesn't exist")
        self._num_groups += 1
        return True
Example #4
0
 def lock(self):
     """This method locks the database."""
     
     self.password = None
     self.keyfile = None
     self.groups[:] = []
     self.entries[:] = []
     self._group_order[:] = []
     self._entry_order[:] = []
     self.root_group = v1Group()
     self._num_groups = 1
     self._num_entries = 0
     return True
Example #5
0
    def lock(self):
        """This method locks the database."""

        self.password = None
        self.keyfile = None
        self.groups[:] = []
        self.entries[:] = []
        self._group_order[:] = []
        self._entry_order[:] = []
        self.root_group = v1Group()
        self._num_groups = 1
        self._num_entries = 0
        return True
Example #6
0
    def create_group(self, title = None, parent = None, image = 1,
                     y = 2999, mon = 12, d = 28, h = 23, min_ = 59,
                     s = 59):
        """This method creates a new group.

        A group title is needed or no group will be created.

        If a parent is given, the group will be created as a sub-group.

        title must be a string, image an unsigned int >0 and parent a v1Group.

        With y, mon, d, h, min_ and s you can set an expiration date like on
        entries.

        """
        
        if title is None:
            raise KPError("Need a group title to create a group.")
        elif type(title) is not str or image < 1 or(parent is not None and \
            type(parent) is not v1Group) or type(image) is not int:
            raise KPError("Wrong type or value for title or image or parent")

        id_ = 1
        for i in self.groups:
            if i.id_ >= id_:
                id_ = i.id_ + 1
        group = v1Group(id_, title, image, self)
        group.creation = datetime.now().replace(microsecond=0)
        group.last_mod = datetime.now().replace(microsecond=0)
        group.last_access = datetime.now().replace(microsecond=0)
        if group.set_expire(y, mon, d, h, min_, s) is False:
            group.set_expire()
        
        # If no parent is given, just append the new group at the end
        if parent is None:
            group.parent = self.root_group
            self.root_group.children.append(group)
            group.level = 0
            self.groups.append(group)
        # Else insert the group behind the parent
        else:
            if parent in self.groups:
                parent.children.append(group)
                group.parent = parent
                group.level = parent.level+1
                self.groups.insert(self.groups.index(parent)+1, group)
            else:
                raise KPError("Given parent doesn't exist")
        self._num_groups += 1
        return True
Example #7
0
    def load(self, buf = None):
        """This method opens an existing database.

        self.password/self.keyfile and self.filepath must be set.
        
        """

        if self.password is None and self.keyfile is None:
            raise KPError('Need a password or keyfile')
        elif self.filepath is None and buf is None:
            raise KPError('Can only load an existing database!')
        
        if buf is None:
            buf = self.read_buf()
        
        # The header is 124 bytes long, the rest is content
        header = buf[:124]
        crypted_content = buf[124:]
        del buf
        
        # The header holds two signatures
        if not (struct.unpack('<I', header[:4])[0] == 0x9AA2D903 and
                struct.unpack('<I', header[4:8])[0] == 0xB54BFB65):
            del crypted_content
            del header
            raise KPError('Wrong signatures!')

        # Unpack the header
        self._enc_flag = struct.unpack('<I', header[8:12])[0]
        self._version = struct.unpack('<I', header[12:16])[0]
        self._final_randomseed = struct.unpack('<16s', header[16:32])[0]
        self._enc_iv = struct.unpack('<16s', header[32:48])[0]
        self._num_groups = struct.unpack('<I', header[48:52])[0]
        self._num_entries = struct.unpack('<I', header[52:56])[0]
        self._contents_hash = struct.unpack('<32s', header[56:88])[0]
        self._transf_randomseed = struct.unpack('<32s', header[88:120])[0]
        self._key_transf_rounds = struct.unpack('<I', header[120:124])[0]
        del header

        # Check if the database is supported
        if self._version & 0xFFFFFF00 != 0x00030002 & 0xFFFFFF00:
            del crypted_content
            raise KPError('Unsupported file version!')
        #Actually, only AES is supported.
        elif not self._enc_flag & 2:
            del crypted_content
            raise KPError('Unsupported file encryption!')
        
        if self.password is None:
            masterkey = self._get_filekey()
        elif self.password is not None and self.keyfile is not None:
            passwordkey = self._get_passwordkey()
            filekey = self._get_filekey()
            sha = SHA256.new()
            sha.update(passwordkey+filekey)
            masterkey = sha.digest()
        else:
            masterkey = self._get_passwordkey()

        # Create the key that is needed to...
        final_key = self._transform_key(masterkey)
        # ...decrypt the content
        decrypted_content = self._cbc_decrypt(final_key, crypted_content)

        # Check if decryption failed
        if ((len(decrypted_content) > 2147483446) or
            (len(decrypted_content) == 0 and self._num_groups > 0)):
            del decrypted_content
            del crypted_content
            raise KPError("Decryption failed!\nThe key is wrong or the file is"
                          " damaged.")

        sha_obj = SHA256.new()
        sha_obj.update(decrypted_content)
        if not self._contents_hash == sha_obj.digest():
            del masterkey
            del final_key
            raise KPError("Hash test failed.\nThe key is wrong or the file is "
                          "damaged.")
        del masterkey
        del final_key

        # Read out the groups
        pos = 0
        levels = []
        cur_group = 0
        group = v1Group()

        while cur_group < self._num_groups:
            # Every group is made up of single fields
            field_type = struct.unpack('<H', decrypted_content[:2])[0]
            decrypted_content = decrypted_content[2:]
            pos += 2

            # Check if offset is alright
            if pos >= len(crypted_content)+124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G1]')
            
            field_size = struct.unpack('<I', decrypted_content[:4])[0]
            decrypted_content = decrypted_content[4:]
            pos += 4
            
            if pos >= len(crypted_content)+124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G2]')

            # Finally read out the content
            b_ret = self._read_group_field(group, levels, field_type,
                                           field_size, decrypted_content)

            # If the end of a group is reached append it to the groups array
            if field_type == 0xFFFF and b_ret == True:
                group.db = self
                self.groups.append(group)
                group = v1Group()
                cur_group += 1
            
            decrypted_content = decrypted_content[field_size:]

            if pos >= len(crypted_content)+124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G1]')

        # Now the same with the entries
        cur_entry = 0
        entry = v1Entry()
        
        while cur_entry < self._num_entries:
            field_type = struct.unpack('<H', decrypted_content[:2])[0]
            decrypted_content = decrypted_content[2:]
            pos += 2
             
            if pos >= len(crypted_content)+124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G1]')
            
            field_size = struct.unpack('<I', decrypted_content[:4])[0]
            decrypted_content = decrypted_content[4:]
            pos += 4
            
            if pos >= len(crypted_content)+124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G2]')
            
            b_ret = self._read_entry_field(entry, field_type, field_size,
                                      decrypted_content)
            
            if field_type == 0xFFFF and b_ret == True:
                self.entries.append(entry)
                if entry.group_id is None:
                    del decrypted_content
                    del crypted_content
                    raise KPError("Found entry without group!")

                entry = v1Entry()
                cur_entry += 1
            
            decrypted_content = decrypted_content[field_size:]
            pos += field_size
            
            if pos >= len(crypted_content)+124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G1]')

        if self._create_group_tree(levels) is False:
            del decrypted_content
            del crypted_content
            return False

        del decrypted_content
        del crypted_content

        if self.filepath is not None:
            with open(self.filepath+'.lock', 'w') as handler:
                handler.write('')
        return True
Example #8
0
    def load(self, buf=None):
        """This method opens an existing database.

        self.password/self.keyfile and self.filepath must be set.
        
        """

        if self.password is None and self.keyfile is None:
            raise KPError('Need a password or keyfile')
        elif self.filepath is None and buf is None:
            raise KPError('Can only load an existing database!')

        if buf is None:
            buf = self.read_buf()

        # The header is 124 bytes long, the rest is content
        header = buf[:124]
        crypted_content = buf[124:]
        del buf

        # The header holds two signatures
        if not (struct.unpack('<I', header[:4])[0] == 0x9AA2D903
                and struct.unpack('<I', header[4:8])[0] == 0xB54BFB65):
            del crypted_content
            del header
            raise KPError('Wrong signatures!')

        # Unpack the header
        self._enc_flag = struct.unpack('<I', header[8:12])[0]
        self._version = struct.unpack('<I', header[12:16])[0]
        self._final_randomseed = struct.unpack('<16s', header[16:32])[0]
        self._enc_iv = struct.unpack('<16s', header[32:48])[0]
        self._num_groups = struct.unpack('<I', header[48:52])[0]
        self._num_entries = struct.unpack('<I', header[52:56])[0]
        self._contents_hash = struct.unpack('<32s', header[56:88])[0]
        self._transf_randomseed = struct.unpack('<32s', header[88:120])[0]
        self._key_transf_rounds = struct.unpack('<I', header[120:124])[0]
        del header

        # Check if the database is supported
        if self._version & 0xFFFFFF00 != 0x00030002 & 0xFFFFFF00:
            del crypted_content
            raise KPError('Unsupported file version!')
        #Actually, only AES is supported.
        elif not self._enc_flag & 2:
            del crypted_content
            raise KPError('Unsupported file encryption!')

        if self.password is None:
            masterkey = self._get_filekey()
        elif self.password is not None and self.keyfile is not None:
            passwordkey = self._get_passwordkey()
            filekey = self._get_filekey()
            sha = SHA256.new()
            sha.update(passwordkey + filekey)
            masterkey = sha.digest()
        else:
            masterkey = self._get_passwordkey()

        # Create the key that is needed to...
        final_key = self._transform_key(masterkey)
        # ...decrypt the content
        decrypted_content = self._cbc_decrypt(final_key, crypted_content)

        # Check if decryption failed
        if ((len(decrypted_content) > 2147483446)
                or (len(decrypted_content) == 0 and self._num_groups > 0)):
            del decrypted_content
            del crypted_content
            raise KPError("Decryption failed!\nThe key is wrong or the file is"
                          " damaged.")

        sha_obj = SHA256.new()
        sha_obj.update(decrypted_content)
        if not self._contents_hash == sha_obj.digest():
            del masterkey
            del final_key
            raise KPError("Hash test failed.\nThe key is wrong or the file is "
                          "damaged.")
        del masterkey
        del final_key

        # Read out the groups
        pos = 0
        levels = []
        cur_group = 0
        group = v1Group()

        while cur_group < self._num_groups:
            # Every group is made up of single fields
            field_type = struct.unpack('<H', decrypted_content[:2])[0]
            decrypted_content = decrypted_content[2:]
            pos += 2

            # Check if offset is alright
            if pos >= len(crypted_content) + 124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G1]')

            field_size = struct.unpack('<I', decrypted_content[:4])[0]
            decrypted_content = decrypted_content[4:]
            pos += 4

            if pos >= len(crypted_content) + 124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G2]')

            # Finally read out the content
            b_ret = self._read_group_field(group, levels, field_type,
                                           field_size, decrypted_content)

            # If the end of a group is reached append it to the groups array
            if field_type == 0xFFFF and b_ret == True:
                group.db = self
                self.groups.append(group)
                group = v1Group()
                cur_group += 1

            decrypted_content = decrypted_content[field_size:]

            if pos >= len(crypted_content) + 124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G1]')

        # Now the same with the entries
        cur_entry = 0
        entry = v1Entry()

        while cur_entry < self._num_entries:
            field_type = struct.unpack('<H', decrypted_content[:2])[0]
            decrypted_content = decrypted_content[2:]
            pos += 2

            if pos >= len(crypted_content) + 124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G1]')

            field_size = struct.unpack('<I', decrypted_content[:4])[0]
            decrypted_content = decrypted_content[4:]
            pos += 4

            if pos >= len(crypted_content) + 124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G2]')

            b_ret = self._read_entry_field(entry, field_type, field_size,
                                           decrypted_content)

            if field_type == 0xFFFF and b_ret == True:
                self.entries.append(entry)
                if entry.group_id is None:
                    del decrypted_content
                    del crypted_content
                    raise KPError("Found entry without group!")

                entry = v1Entry()
                cur_entry += 1

            decrypted_content = decrypted_content[field_size:]
            pos += field_size

            if pos >= len(crypted_content) + 124:
                del decrypted_content
                del crypted_content
                raise KPError('Unexpected error: Offset is out of range.[G1]')

        if self._create_group_tree(levels) is False:
            del decrypted_content
            del crypted_content
            return False

        del decrypted_content
        del crypted_content

        if self.filepath is not None:
            with open(self.filepath + '.lock', 'w') as handler:
                handler.write('')
        return True