From ba873b92264d670c5e599bb9886c1739d95e38e2 Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Fri, 13 Dec 2013 19:06:53 +0300 Subject: [PATCH 01/16] move new_entry and new_group to infoblock.info --- python/keepass/hier.py | 25 ++------------- python/keepass/infoblock.py | 62 +++++++++++++++++++++++++++++++++++-- python/keepass/kpdb.py | 42 ++----------------------- 3 files changed, 64 insertions(+), 65 deletions(-) diff --git a/python/keepass/hier.py b/python/keepass/hier.py index 22f4d67..020c702 100644 --- a/python/keepass/hier.py +++ b/python/keepass/hier.py @@ -10,8 +10,6 @@ # Free Software Foundation; either version 2, or (at your option) any # later version. -import datetime - def path2list(path): ''' Maybe convert a '/' separated string into a list. @@ -322,27 +320,8 @@ def mkdir(top, path, gen_groupid): node = fg.best_match or top pathlen -= len(fg.path) for group_name in fg.path: - # fixme, this should be moved into a new constructor - new_group = infoblock.GroupInfo() - new_group.groupid = gen_groupid() - new_group.group_name = group_name - new_group.imageid = 1 - new_group.creation_time = datetime.datetime.now() - new_group.last_mod_time = datetime.datetime.now() - new_group.last_acc_time = datetime.datetime.now() - new_group.expiration_time = datetime.datetime(2999, 12, 28, 23, 59, 59) # KeePassX 0.4.3 default - new_group.level = pathlen - new_group.flags = 0 - new_group.order = [(1, 4), - (2, len(new_group.group_name) + 1), - (3, 5), - (4, 5), - (5, 5), - (6, 5), - (7, 4), - (8, 2), - (9, 4), - (65535, 0)] + new_group = infoblock.GroupInfo().make_group(group_name, pathlen, gen_groupid()) + pathlen += 1 new_node = Node(new_group) diff --git a/python/keepass/infoblock.py b/python/keepass/infoblock.py index 451151f..afb22f5 100644 --- a/python/keepass/infoblock.py +++ b/python/keepass/infoblock.py @@ -13,6 +13,8 @@ import struct import sys +import datetime +import uuid # return tupleof (decode,encode) functions @@ -171,13 +173,36 @@ class GroupInfo(InfoBase): 0xFFFF: (None,None), } - def __init__(self,string=None): - super(GroupInfo,self).__init__(GroupInfo.format,string) + def __init__(self, string=None): + super(GroupInfo,self).__init__(GroupInfo.format, string) return def name(self): 'Return the group_name' return self.group_name + + def make_group(self, group_name, pathlen, gen_groupid): + new_group = GroupInfo() + new_group.groupid = gen_groupid + new_group.group_name = group_name + new_group.imageid = 1 + new_group.creation_time = datetime.datetime.now() + new_group.last_mod_time = datetime.datetime.now() + new_group.last_acc_time = datetime.datetime.now() + new_group.expiration_time = datetime.datetime(2999, 12, 28, 23, 59, 59) # KeePassX 0.4.3 default + new_group.level = pathlen + new_group.flags = 0 + new_group.order = [(1, 4), + (2, len(new_group.group_name) + 1), + (3, 5), + (4, 5), + (5, 5), + (6, 5), + (7, 4), + (8, 2), + (9, 4), + (65535, 0)] + return new_group pass @@ -237,6 +262,39 @@ def __init__(self,string=None): def name(self): 'Return the title' return self.title + + def make_entry(self,node,title,username,password,url="",notes="",imageid=1): + new_entry = EntryInfo() + new_entry.uuid = uuid.uuid4().hex + new_entry.groupid = node.group.groupid + new_entry.imageid = imageid + new_entry.title = title + new_entry.url = url + new_entry.username = username + new_entry.password = password + new_entry.notes = notes + new_entry.creation_time = datetime.datetime.now() + new_entry.last_mod_time = datetime.datetime.now() + new_entry.last_acc_time = datetime.datetime.now() + new_entry.expiration_time = datetime.datetime(2999, 12, 28, 23, 59, 59) # KeePassX 0.4.3 default + new_entry.binary_desc = "" + new_entry.binary_data = None + new_entry.order = [(1, 16), + (2, 4), + (3, 4), + (4, len(title) + 1), + (5, len(url) + 1), + (6, len(username) + 1), + (7, len(password) + 1), + (8, len(notes) + 1), + (9, 5), + (10, 5), + (11, 5), + (12, 5), + (13, len(new_entry.binary_desc) + 1), + (14, 0), + (65535, 0)] + return new_entry pass diff --git a/python/keepass/kpdb.py b/python/keepass/kpdb.py index db79f24..f2099cc 100644 --- a/python/keepass/kpdb.py +++ b/python/keepass/kpdb.py @@ -19,8 +19,6 @@ # later version. import sys, struct, os -import datetime -import uuid import random from copy import copy @@ -286,54 +284,18 @@ def add_entry(self,path,title,username,password,url="",notes="",imageid=1,append top = self.hierarchy() node = hier.mkdir(top, path, self.gen_groupid) - - # fixme, this should probably be moved into a new constructor - def make_entry(): - new_entry = infoblock.EntryInfo() - new_entry.uuid = uuid.uuid4().hex - new_entry.groupid = node.group.groupid - new_entry.imageid = imageid - new_entry.title = title - new_entry.url = url - new_entry.username = username - new_entry.password = password - new_entry.notes = notes - new_entry.creation_time = datetime.datetime.now() - new_entry.last_mod_time = datetime.datetime.now() - new_entry.last_acc_time = datetime.datetime.now() - new_entry.expiration_time = datetime.datetime(2999, 12, 28, 23, 59, 59) # KeePassX 0.4.3 default - new_entry.binary_desc = "" - new_entry.binary_data = None - new_entry.order = [(1, 16), - (2, 4), - (3, 4), - (4, len(title) + 1), - (5, len(url) + 1), - (6, len(username) + 1), - (7, len(password) + 1), - (8, len(notes) + 1), - (9, 5), - (10, 5), - (11, 5), - (12, 5), - (13, len(new_entry.binary_desc) + 1), - (14, 0), - (65535, 0)] - #new_entry.None = None - #fixme, deal with times - return new_entry existing_node_updated = False if not append: for i, ent in enumerate(node.entries): if ent.title != title: continue if ent.username != username: continue - node.entries[i] = make_entry() + node.entries[i] = infoblock.EntryInfo().make_entry(node,title,username,password,url,notes,imageid) existing_node_updated = True break if not existing_node_updated: - node.entries.append(make_entry()) + node.entries.append(infoblock.EntryInfo().make_entry(node,title,username,password,url,notes,imageid)) self.update_by_hierarchy(top) From 90d76726779f2ac8e6fce51ee859a1da2435fd07 Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Fri, 13 Dec 2013 22:45:20 +0300 Subject: [PATCH 02/16] finished inicialazing an empty database --- python/keepass/header.py | 3 +++ python/keepass/hier.py | 6 +++-- python/keepass/infoblock.py | 4 +-- python/keepass/kpdb.py | 49 ++++++++++++++++++++----------------- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/python/keepass/header.py b/python/keepass/header.py index a488451..a19a4da 100644 --- a/python/keepass/header.py +++ b/python/keepass/header.py @@ -86,6 +86,9 @@ def __init__(self,buf=None): self.version = 0x30002 self.flags = 3 # SHA2 hashing, AES encryption self.key_enc_rounds = 50000 + self.ngroups = 0 + self.nentries = 0 + self.contents_hash = "" self.reset_random_fields() def reset_random_fields(self): diff --git a/python/keepass/hier.py b/python/keepass/hier.py index 020c702..26aca44 100644 --- a/python/keepass/hier.py +++ b/python/keepass/hier.py @@ -301,7 +301,7 @@ def walk(node,walker): continue return None -def mkdir(top, path, gen_groupid): +def mkdir(top, path, groupid, groups, header): ''' Starting at given top node make nodes and groups to satisfy the given path, where needed. Return the node holding the leaf group. @@ -320,7 +320,7 @@ def mkdir(top, path, gen_groupid): node = fg.best_match or top pathlen -= len(fg.path) for group_name in fg.path: - new_group = infoblock.GroupInfo().make_group(group_name, pathlen, gen_groupid()) + new_group = infoblock.GroupInfo().make_group(group_name, pathlen, groupid) pathlen += 1 @@ -329,6 +329,8 @@ def mkdir(top, path, gen_groupid): node = new_node group = new_group + groups.append(new_group) + header.ngroups += 1 continue pass return node diff --git a/python/keepass/infoblock.py b/python/keepass/infoblock.py index afb22f5..76f6ad1 100644 --- a/python/keepass/infoblock.py +++ b/python/keepass/infoblock.py @@ -181,9 +181,9 @@ def name(self): 'Return the group_name' return self.group_name - def make_group(self, group_name, pathlen, gen_groupid): + def make_group(self, group_name, pathlen, groupid): new_group = GroupInfo() - new_group.groupid = gen_groupid + new_group.groupid = groupid new_group.group_name = group_name new_group.imageid = 1 new_group.creation_time = datetime.datetime.now() diff --git a/python/keepass/kpdb.py b/python/keepass/kpdb.py index f2099cc..3962053 100644 --- a/python/keepass/kpdb.py +++ b/python/keepass/kpdb.py @@ -38,6 +38,9 @@ def __init__(self, filename = None, masterkey=""): self.header = DBHDR() self.groups = [] self.entries = [] + # add basic structure + self.add_entry("Internet","Meta-Info","SYSTEM","","$","KPX_CUSTOM_ICONS_4") + self.add_entry("eMail","Meta-Info","SYSTEM","","$","KPX_GROUP_TREE_STATE") return def read(self,filename): @@ -157,28 +160,29 @@ def encode_payload(self): payload += entry.encode() return payload + def generate_contents_hash(self): + import hashlib + self.header.contents_hash = hashlib.sha256(self.encode_payload()).digest() + def write(self,filename,masterkey=""): '''' Write out DB to given filename with optional master key. If no master key is given, the one used to create this DB is used. Resets IVs and master seeds. ''' - import hashlib + self.generate_contents_hash() header = copy(self.header) header.ngroups = len(self.groups) header.nentries = len(self.entries) header.reset_random_fields() - payload = self.encode_payload() - header.contents_hash = hashlib.sha256(payload).digest() - finalkey = self.final_key(masterkey = masterkey or self.masterkey, masterseed = header.master_seed, masterseed2 = header.master_seed2, rounds = header.key_enc_rounds) - payload = self.encrypt_payload(payload, finalkey, + payload = self.encrypt_payload(self.encode_payload(), finalkey, header.encryption_type(), header.encryption_iv) @@ -233,7 +237,7 @@ def hierarchy(self): breadcrumb[-1].nodes.append(n) breadcrumb.append(n) continue - + for ent in self.entries: n = node_by_id[ent.groupid] n.entries.append(ent) @@ -250,6 +254,7 @@ def update_by_hierarchy(self, hierarchy): hier.visit(hierarchy, collector) self.groups = collector.groups self.entries = collector.entries + self.generate_contents_hash() return def gen_groupid(self): @@ -273,8 +278,9 @@ def update_entry(self,title,username,url,notes="",new_title=None,new_username=No if new_url: entry.url = new_url if new_notes: entry.notes = new_notes entry.new_entry.last_mod_time = datetime.datetime.now() + self.generate_contents_hash() - def add_entry(self,path,title,username,password,url="",notes="",imageid=1,append=True): + def add_entry(self,path,title,username,password,url="",notes="",imageid=1): ''' Add an entry to the current database at with given values. If append is False a pre-existing entry that matches path, title @@ -283,26 +289,21 @@ def add_entry(self,path,title,username,password,url="",notes="",imageid=1,append import hier, infoblock top = self.hierarchy() - node = hier.mkdir(top, path, self.gen_groupid) + node = hier.mkdir(top, path, self.gen_groupid(), self.groups, self.header) - existing_node_updated = False - if not append: - for i, ent in enumerate(node.entries): - if ent.title != title: continue - if ent.username != username: continue - node.entries[i] = infoblock.EntryInfo().make_entry(node,title,username,password,url,notes,imageid) - existing_node_updated = True - break - - if not existing_node_updated: - node.entries.append(infoblock.EntryInfo().make_entry(node,title,username,password,url,notes,imageid)) - - self.update_by_hierarchy(top) + new_entry = infoblock.EntryInfo().make_entry(node,title,username,password,url,notes,imageid) + + self.entries.append(new_entry) + self.header.nentries += 1 + + self.generate_contents_hash() def remove_entry(self, username, url): for entry in self.entries: if entry.username == str(username) and entry.url == str(url): self.entries.remove(entry) + self.header.nentries -= 1 + self.generate_contents_hash() def remove_group(self, path, level=None): for group in self.groups: @@ -310,15 +311,19 @@ def remove_group(self, path, level=None): if level: if group.level == level: self.groups.remove(group) + self.header.ngroups -= 1 for entry in self.entries: if entry.groupid == group.groupid: self.entries.remove(entry) + self.header.nentries -= 1 else: self.groups.remove(group) + self.header.ngroups -= 1 for entry in self.entries: if entry.groupid == group.groupid: self.entries.remove(entry) - + self.header.nentries -= 1 + self.generate_contents_hash() pass From 382c1c402c46a896dcf0621f5e0749881a5ad649 Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Fri, 13 Dec 2013 23:06:26 +0300 Subject: [PATCH 03/16] add add_group and update_group functions, fixed update_entry --- python/keepass/kpdb.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/python/keepass/kpdb.py b/python/keepass/kpdb.py index 3962053..c04c87d 100644 --- a/python/keepass/kpdb.py +++ b/python/keepass/kpdb.py @@ -21,9 +21,11 @@ import sys, struct, os import random from copy import copy +import datetime from header import DBHDR from infoblock import GroupInfo, EntryInfo +import hier class Database(object): ''' @@ -269,7 +271,7 @@ def gen_groupid(self): if groupid not in existing_groupids: return groupid - def update_entry(self,title,username,url,notes="",new_title=None,new_username=None,new_password=None,new_url=None,new_notes=None): + def update_entry(self,title,username,url,notes="",new_title="",new_username="",new_password="",new_url="",new_notes=""): for entry in self.entries: if entry.title == str(title) and entry.username == str(username) and entry.url == str(url): if new_title: entry.title = new_title @@ -277,7 +279,7 @@ def update_entry(self,title,username,url,notes="",new_title=None,new_username=No if new_password: entry.password = new_password if new_url: entry.url = new_url if new_notes: entry.notes = new_notes - entry.new_entry.last_mod_time = datetime.datetime.now() + entry.last_mod_time = datetime.datetime.now() self.generate_contents_hash() def add_entry(self,path,title,username,password,url="",notes="",imageid=1): @@ -286,18 +288,30 @@ def add_entry(self,path,title,username,password,url="",notes="",imageid=1): append is False a pre-existing entry that matches path, title and username will be overwritten with the new one. ''' - import hier, infoblock top = self.hierarchy() node = hier.mkdir(top, path, self.gen_groupid(), self.groups, self.header) - new_entry = infoblock.EntryInfo().make_entry(node,title,username,password,url,notes,imageid) + new_entry = EntryInfo().make_entry(node,title,username,password,url,notes,imageid) self.entries.append(new_entry) self.header.nentries += 1 self.generate_contents_hash() + def update_group(self, group_name="", groupid="", pathlen="", new_group_name="", new_groupid="", new_pathlen=""): + for group in self.groups: + if (group_name and group.group_name == group_name) and (groupid and group.groupid == groupid) and (pathlen and group.level == pathlen): + if new_group_name: group.group_name = new_group_name + if new_groupid: group.groupid = new_groupid + if new_pathlen: group.pathlen = new_pathlen + group.last_mod_time = datetime.datetime.now() + self.generate_contents_hash() + + def add_group(self, path): + hier.mkdir(self.hierarchy(), path, self.gen_groupid(), self.groups, self.header) + self.generate_contents_hash() + def remove_entry(self, username, url): for entry in self.entries: if entry.username == str(username) and entry.url == str(url): From de223ae5018cb45aa27138720c88f449f02c9680 Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Fri, 13 Dec 2013 23:19:44 +0300 Subject: [PATCH 04/16] changed test_hier nad test_io because hier.mkdir and kpdb.Database has been changed --- tests/test_hier.py | 8 +++++++- tests/test_io.py | 10 +++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/test_hier.py b/tests/test_hier.py index 1873ac8..18747e1 100644 --- a/tests/test_hier.py +++ b/tests/test_hier.py @@ -1,5 +1,11 @@ from keepass import hier +groups = [] + +class Header(): + def __init__(self): + self.ngroups = 0 + class GroupIDGenerator(object): def __init__(self): self.groupid = 0 @@ -10,7 +16,7 @@ def gen_groupid(self): def test_hierarchy(): top = hier.Node() - hier.mkdir(top, 'SubDir/SubSubDir', GroupIDGenerator().gen_groupid) + hier.mkdir(top, 'SubDir/SubSubDir', GroupIDGenerator().gen_groupid, groups, Header()) dumper = hier.NodeDumper() hier.walk(top,dumper) diff --git a/tests/test_io.py b/tests/test_io.py index 7e4f9a7..d45cada 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -14,15 +14,15 @@ def test_write(): try: db = keepass.kpdb.Database() db.add_entry(path='Secrets/Terrible', title='Gonk', username='foo', password='bar', url='https://example.org/') - assert len(db.groups) == 2 - assert len(db.entries) == 1 + assert len(db.groups) == 4 + assert len(db.entries) == 3 db.write(kdb_path, password) assert os.path.isfile(kdb_path) db2 = keepass.kpdb.Database(kdb_path, password) - assert len(db.groups) == 2 - assert len(db.entries) == 1 - assert db.entries[0].name() == 'Gonk' + assert len(db.groups) == 4 + assert len(db.entries) == 3 + assert db.entries[2].name() == 'Gonk' finally: shutil.rmtree(tempdir) From 643f73e996b52fcbd4be9ee5a63089198a961a8f Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Thu, 2 Jan 2014 01:31:01 +0300 Subject: [PATCH 05/16] first step of porting to python3 --- python/keepass/cli.py | 29 +++++++++++++---------------- python/keepass/header.py | 6 +++--- python/keepass/hier.py | 17 +++++++---------- python/keepass/infoblock.py | 28 ++++++++++++++-------------- python/keepass/kpdb.py | 34 ++++++++++++++-------------------- 5 files changed, 51 insertions(+), 63 deletions(-) diff --git a/python/keepass/cli.py b/python/keepass/cli.py index bdfb022..eb17dbe 100644 --- a/python/keepass/cli.py +++ b/python/keepass/cli.py @@ -11,6 +11,10 @@ # later version. import sys +import six +from optparse import OptionParser +import getpass +import kpdb class Cli(object): ''' @@ -49,7 +53,7 @@ def splitopts(argv): cmd="" if argv[0][0] != '-': if argv[0] not in Cli.commands: - raise ValueError,'Unknown command: "%s"'%argv[0] + six.reraise(ValueError,'Unknown command: "%s"'%argv[0]) cmd = argv.pop(0) pass copy = list(argv) @@ -79,7 +83,7 @@ def splitopts(argv): def __call__(self): 'Process commands' if not self.command_line: - print self._general_op().print_help() + six.print_((self._general_op().print_help())) return for cmd,cmdopts in self.command_line: meth = eval('self._%s'%cmd) @@ -99,7 +103,6 @@ def _general_op(self): execute "help" command for more information. ''' - from optparse import OptionParser op = OptionParser(usage=self._general_op.__doc__) return op @@ -114,25 +117,24 @@ def _help_op(self): def _help(self,opts): 'Print some helpful information' - print 'Available commands:' + six.print_(('Available commands:')) for cmd in Cli.commands: meth = eval('self._%s'%cmd) - print '\t%s: %s'%(cmd,meth.__doc__) + six.print_(('\t%s: %s'%(cmd,meth.__doc__))) continue - print '\nPer-command help:\n' + six.print_(('\nPer-command help:\n')) for cmd in Cli.commands: meth = eval('self._%s_op'%cmd) op = meth() if not op: continue - print '%s'%cmd.upper() + six.print_(('%s'%cmd.upper())) op.print_help() - print + six.print_(()) continue def _open_op(self): 'open [options] filename' - from optparse import OptionParser op = OptionParser(usage=self._open_op.__doc__,add_help_option=False) op.add_option('-m','--masterkey',type='string',default="", help='Set master key for decrypting file, default: ""') @@ -141,12 +143,11 @@ def _open_op(self): def _open(self,opts): 'Read a file to the in-memory database' opts,files = self.ops['open'].parse_args(opts) - import kpdb # fixme - add support for openning/merging multiple DBs! try: dbfile = files[0] except IndexError: - print "No database file specified" + six.print_(("No database file specified")) sys.exit(1) self.db = kpdb.Database(files[0],opts.masterkey) self.hier = self.db.hierarchy() @@ -154,7 +155,6 @@ def _open(self,opts): def _save_op(self): 'save [options] filename' - from optparse import OptionParser op = OptionParser(usage=self._save_op.__doc__,add_help_option=False) op.add_option('-m','--masterkey',type='string',default="", help='Set master key for encrypting file, default: ""') @@ -169,7 +169,6 @@ def _save(self,opts): def _dump_op(self): 'dump [options] [name|/group/name]' - from optparse import OptionParser op = OptionParser(usage=self._dump_op.__doc__,add_help_option=False) op.add_option('-p','--show-passwords',action='store_true',default=False, help='Show passwords as plain text') @@ -184,13 +183,12 @@ def _dump(self,opts): if not self.hier: sys.stderr.write('Can not dump. No database open.\n') return - print self.hier + six.print_((self.hier)) #self.hier.dump(opts.format,opts.show_passwords) return def _entry_op(self): 'entry [options] username [password]' - from optparse import OptionParser op = OptionParser(usage=self._entry_op.__doc__,add_help_option=False) op.add_option('-p','--path',type='string',default='/', help='Set folder path in which to store this entry') @@ -208,7 +206,6 @@ def _entry_op(self): def _entry(self,opts): 'Add an entry into the database' - import getpass opts,args = self.ops['entry'].parse_args(opts) username = args[0] try: diff --git a/python/keepass/header.py b/python/keepass/header.py index a19a4da..1f97f9e 100644 --- a/python/keepass/header.py +++ b/python/keepass/header.py @@ -45,6 +45,8 @@ # later version. import Crypto.Random +import struct +import six class DBHDR(object): ''' @@ -115,7 +117,6 @@ def encryption_type(self): def encode(self): 'Provide binary string representation' - import struct ret = "" @@ -129,7 +130,6 @@ def encode(self): def decode(self,buf): 'Fill self from binary string.' - import struct index = 0 @@ -146,7 +146,7 @@ def decode(self,buf): msg = 'Bad sigs:\n%s %s\n%s %s'%\ (DBHDR.signatures[0],DBHDR.signatures[1], self.signature1,self.signature2) - raise IOError,msg + six.reraise(IOError,msg) return diff --git a/python/keepass/hier.py b/python/keepass/hier.py index 26aca44..302a741 100644 --- a/python/keepass/hier.py +++ b/python/keepass/hier.py @@ -10,6 +10,9 @@ # Free Software Foundation; either version 2, or (at your option) any # later version. +from keepass.infoblock import GroupInfo +import six + def path2list(path): ''' Maybe convert a '/' separated string into a list. @@ -60,10 +63,10 @@ def __call__(self,node): class NodeDumper(Walker): def __call__(self,node): if not node.group: - print 'Top' + six.print_(('Top')) return None, False - print ' '*node.level()*2,node.group.name(),node.group.groupid,\ - len(node.entries),len(node.nodes) + six.print_((' '*node.level()*2,node.group.name(),node.group.groupid,\ + len(node.entries),len(node.nodes))) return None, False class FindGroupNode(object): @@ -90,8 +93,6 @@ def __call__(self,node): groupid = node.group.groupid - from infoblock import GroupInfo - if top_name != obj_name: return (None,True) # bail on the current node @@ -122,7 +123,6 @@ def __init__(self): def __call__(self,g_or_e): if g_or_e is None: return (None,None) - from infoblock import GroupInfo if isinstance(g_or_e,GroupInfo): self.groups.append(g_or_e) else: @@ -174,8 +174,6 @@ def __call__(self,g_or_e): groupid = None if g_or_e: groupid = g_or_e.groupid - from infoblock import GroupInfo - if top_name != obj_name: if isinstance(g_or_e,GroupInfo): return (None,True) # bail on the current node @@ -308,7 +306,6 @@ def mkdir(top, path, groupid, groups, header): @param gen_groupid: Group ID factory from kpdb.Database instance. ''' - import infoblock path = path2list(path) pathlen = len(path) @@ -320,7 +317,7 @@ def mkdir(top, path, groupid, groups, header): node = fg.best_match or top pathlen -= len(fg.path) for group_name in fg.path: - new_group = infoblock.GroupInfo().make_group(group_name, pathlen, groupid) + new_group = GroupInfo().make_group(group_name, pathlen, groupid) pathlen += 1 diff --git a/python/keepass/infoblock.py b/python/keepass/infoblock.py index 76f6ad1..143599e 100644 --- a/python/keepass/infoblock.py +++ b/python/keepass/infoblock.py @@ -13,8 +13,10 @@ import struct import sys -import datetime +from datetime import datetime import uuid +import six +from binascii import b2a_hex, a2b_hex # return tupleof (decode,encode) functions @@ -22,7 +24,6 @@ def null_de(): return (lambda buf:None, lambda val:None) def shunt_de(): return (lambda buf:buf, lambda val:val) def ascii_de(): - from binascii import b2a_hex, a2b_hex return (lambda buf:b2a_hex(buf).replace('\0',''), lambda val:a2b_hex(val)+'\0') @@ -38,7 +39,6 @@ def int_de(): lambda val:struct.pack("> 2); @@ -96,10 +96,10 @@ def decode(self,string): if name is None: break try: value = decenc[0](buf) - except struct.error,msg: + except struct.error as msg: msg = '%s, typ = %d[%d] -> %s buf = "%s"'%\ (msg,typ,siz,self.format[typ],buf) - raise struct.error,msg + six.reraise(struct.error,msg) self.__dict__[name] = value continue @@ -113,7 +113,7 @@ def __len__(self): def encode(self): 'Return binary string representatoin' - string = "" + string = b"" for typ,siz in self.order: if typ == 0xFFFF: # end of block encoded = None @@ -186,10 +186,10 @@ def make_group(self, group_name, pathlen, groupid): new_group.groupid = groupid new_group.group_name = group_name new_group.imageid = 1 - new_group.creation_time = datetime.datetime.now() - new_group.last_mod_time = datetime.datetime.now() - new_group.last_acc_time = datetime.datetime.now() - new_group.expiration_time = datetime.datetime(2999, 12, 28, 23, 59, 59) # KeePassX 0.4.3 default + new_group.creation_time = datetime.now() + new_group.last_mod_time = datetime.now() + new_group.last_acc_time = datetime.now() + new_group.expiration_time = datetime(2999, 12, 28, 23, 59, 59) # KeePassX 0.4.3 default new_group.level = pathlen new_group.flags = 0 new_group.order = [(1, 4), @@ -273,10 +273,10 @@ def make_entry(self,node,title,username,password,url="",notes="",imageid=1): new_entry.username = username new_entry.password = password new_entry.notes = notes - new_entry.creation_time = datetime.datetime.now() - new_entry.last_mod_time = datetime.datetime.now() - new_entry.last_acc_time = datetime.datetime.now() - new_entry.expiration_time = datetime.datetime(2999, 12, 28, 23, 59, 59) # KeePassX 0.4.3 default + new_entry.creation_time = datetime.now() + new_entry.last_mod_time = datetime.now() + new_entry.last_acc_time = datetime.now() + new_entry.expiration_time = datetime(2999, 12, 28, 23, 59, 59) # KeePassX 0.4.3 default new_entry.binary_desc = "" new_entry.binary_data = None new_entry.order = [(1, 16), diff --git a/python/keepass/kpdb.py b/python/keepass/kpdb.py index c04c87d..8bdc234 100644 --- a/python/keepass/kpdb.py +++ b/python/keepass/kpdb.py @@ -22,10 +22,13 @@ import random from copy import copy import datetime +import six +from Crypto.Cipher import AES +import hashlib -from header import DBHDR -from infoblock import GroupInfo, EntryInfo -import hier +from keepass.header import DBHDR +from keepass.infoblock import GroupInfo, EntryInfo +from keepass import hier class Database(object): ''' @@ -88,8 +91,6 @@ def final_key(self,masterkey,masterseed,masterseed2,rounds): '''Munge masterkey into the final key for decryping payload by encrypting it for the given number of rounds masterseed2 and hashing it with masterseed.''' - from Crypto.Cipher import AES - import hashlib key = hashlib.sha256(masterkey).digest() cipher = AES.new(masterseed2, AES.MODE_ECB) @@ -105,24 +106,21 @@ def decrypt_payload(self, payload, finalkey, enctype, iv): 'Decrypt payload (non-header) part of the buffer' if enctype != 'Rijndael': - raise ValueError, 'Unsupported decryption type: "%s"'%enctype + six.reraise(ValueError, 'Unsupported decryption type: "%s"'%enctype) payload = self.decrypt_payload_aes_cbc(payload, finalkey, iv) crypto_size = len(payload) if ((crypto_size > 2147483446) or (not crypto_size and self.header.ngroups)): - raise ValueError, "Decryption failed.\nThe key is wrong or the file is damaged" + six.reraise(ValueError, "Decryption failed.\nThe key is wrong or the file is damaged") - import hashlib if self.header.contents_hash != hashlib.sha256(payload).digest(): - raise ValueError, "Decryption failed. The file checksum did not match." + six.reraise(ValueError, "Decryption failed. The file checksum did not match.") return payload def decrypt_payload_aes_cbc(self, payload, finalkey, iv): 'Decrypt payload buffer with AES CBC' - - from Crypto.Cipher import AES cipher = AES.new(finalkey, AES.MODE_CBC, iv) payload = cipher.decrypt(payload) extra = ord(payload[-1]) @@ -132,12 +130,11 @@ def decrypt_payload_aes_cbc(self, payload, finalkey, iv): def encrypt_payload(self, payload, finalkey, enctype, iv): 'Encrypt payload' if enctype != 'Rijndael': - raise ValueError, 'Unsupported encryption type: "%s"'%enctype + six.reraise(ValueError, 'Unsupported encryption type: "%s"'%enctype) return self.encrypt_payload_aes_cbc(payload, finalkey, iv) def encrypt_payload_aes_cbc(self, payload, finalkey, iv): 'Encrypt payload buffer with AES CBC' - from Crypto.Cipher import AES cipher = AES.new(finalkey, AES.MODE_CBC, iv) # pad out and store amount as last value length = len(payload) @@ -163,7 +160,6 @@ def encode_payload(self): return payload def generate_contents_hash(self): - import hashlib self.header.contents_hash = hashlib.sha256(self.encode_payload()).digest() def write(self,filename,masterkey=""): @@ -216,20 +212,19 @@ def dump_entries(self,format,show_passwords=False): if 'group' not in nick: nick = 'group_'+nick dat[nick] = group.__dict__[what] - print format%dat + six.print_((format%dat)) continue return def hierarchy(self): '''Return database with groups and entries organized into a hierarchy''' - from hier import Node - top = Node() + top = hier.Node() breadcrumb = [top] node_by_id = {None:top} for group in self.groups: - n = Node(group) + n = hier.Node(group) node_by_id[group.groupid] = n while group.level - breadcrumb[-1].level() != 1: @@ -251,7 +246,6 @@ def update_by_hierarchy(self, hierarchy): Update the database using the given hierarchy. This replaces the existing groups and entries. ''' - import hier collector = hier.CollectVisitor() hier.visit(hierarchy, collector) self.groups = collector.groups @@ -265,7 +259,7 @@ def gen_groupid(self): """ existing_groupids = {group.groupid for group in self.groups} if len(existing_groupids) >= 0xfffffffe: - raise Exception("All groupids are in use!") + six.reraise(Exception("All groupids are in use!")) while True: groupid = random.randint(1, 0xfffffffe) # 0 and 0xffffffff are reserved if groupid not in existing_groupids: From 157665b22e005281c2dcd33d3b965520ecc2fae4 Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Thu, 2 Jan 2014 16:33:23 +0300 Subject: [PATCH 06/16] writing works fine --- python/keepass/cli.py | 2 +- python/keepass/header.py | 34 ++++++++++++++++------------------ python/keepass/hier.py | 2 +- python/keepass/infoblock.py | 6 ++++-- python/keepass/kpdb.py | 35 ++++++++++++++++++----------------- 5 files changed, 40 insertions(+), 39 deletions(-) diff --git a/python/keepass/cli.py b/python/keepass/cli.py index eb17dbe..39874b6 100644 --- a/python/keepass/cli.py +++ b/python/keepass/cli.py @@ -53,7 +53,7 @@ def splitopts(argv): cmd="" if argv[0][0] != '-': if argv[0] not in Cli.commands: - six.reraise(ValueError,'Unknown command: "%s"'%argv[0]) + raise ValueError('Unknown command: "%s"'%argv[0]) cmd = argv.pop(0) pass copy = list(argv) diff --git a/python/keepass/header.py b/python/keepass/header.py index 1f97f9e..8f95860 100644 --- a/python/keepass/header.py +++ b/python/keepass/header.py @@ -54,17 +54,17 @@ class DBHDR(object): ''' format = [ - ('signature1',4,'I'), - ('signature2',4,'I'), - ('flags',4,'I'), - ('version',4,'I'), - ('master_seed',16,'16s'), - ('encryption_iv',16,'16s'), - ('ngroups',4,'I'), - ('nentries',4,'I'), - ('contents_hash',32,'32s'), - ('master_seed2',32,'32s'), - ('key_enc_rounds',4,'I'), + ('signature1',4,b'I'), + ('signature2',4,b'I'), + ('flags',4,b'I'), + ('version',4,b'I'), + ('master_seed',16,b'16s'), + ('encryption_iv',16,b'16s'), + ('ngroups',4,b'I'), + ('nentries',4,b'I'), + ('contents_hash',32,b'32s'), + ('master_seed2',32,b'32s'), + ('key_enc_rounds',4,b'I'), ] signatures = (0x9AA2D903,0xB54BFB65) @@ -117,26 +117,24 @@ def encryption_type(self): def encode(self): 'Provide binary string representation' - - ret = "" - + ret = b"" for field in DBHDR.format: name,bytes,typecode = field value = self.__dict__[name] - buf = struct.pack('<'+typecode,value) + buf = struct.pack(b'<'+typecode,value) ret += buf continue return ret def decode(self,buf): 'Fill self from binary string.' - index = 0 - for field in DBHDR.format: name,nbytes,typecode = field string = buf[index:index+nbytes] index += nbytes + if six.PY3: + string = string.encode() value = struct.unpack('<'+typecode, string)[0] self.__dict__[name] = value continue @@ -146,7 +144,7 @@ def decode(self,buf): msg = 'Bad sigs:\n%s %s\n%s %s'%\ (DBHDR.signatures[0],DBHDR.signatures[1], self.signature1,self.signature2) - six.reraise(IOError,msg) + raise IOError(msg) return diff --git a/python/keepass/hier.py b/python/keepass/hier.py index 302a741..8ea7fc9 100644 --- a/python/keepass/hier.py +++ b/python/keepass/hier.py @@ -10,7 +10,7 @@ # Free Software Foundation; either version 2, or (at your option) any # later version. -from keepass.infoblock import GroupInfo +from infoblock import GroupInfo import six def path2list(path): diff --git a/python/keepass/infoblock.py b/python/keepass/infoblock.py index 143599e..b1f6118 100644 --- a/python/keepass/infoblock.py +++ b/python/keepass/infoblock.py @@ -25,7 +25,7 @@ def shunt_de(): return (lambda buf:buf, lambda val:val) def ascii_de(): return (lambda buf:b2a_hex(buf).replace('\0',''), - lambda val:a2b_hex(val)+'\0') + lambda val:a2b_hex(val)+b'\0') def string_de(): return (lambda buf: buf.replace('\0',''), lambda val: val+'\0') @@ -99,7 +99,7 @@ def decode(self,string): except struct.error as msg: msg = '%s, typ = %d[%d] -> %s buf = "%s"'%\ (msg,typ,siz,self.format[typ],buf) - six.reraise(struct.error,msg) + raise struct.error(msg) self.__dict__[name] = value continue @@ -125,6 +125,8 @@ def encode(self): buf = struct.pack(' 2147483446) or (not crypto_size and self.header.ngroups)): - six.reraise(ValueError, "Decryption failed.\nThe key is wrong or the file is damaged") + raise ValueError("Decryption failed.\nThe key is wrong or the file is damaged") if self.header.contents_hash != hashlib.sha256(payload).digest(): - six.reraise(ValueError, "Decryption failed. The file checksum did not match.") + raise ValueError("Decryption failed. The file checksum did not match.") return payload @@ -130,7 +131,7 @@ def decrypt_payload_aes_cbc(self, payload, finalkey, iv): def encrypt_payload(self, payload, finalkey, enctype, iv): 'Encrypt payload' if enctype != 'Rijndael': - six.reraise(ValueError, 'Unsupported encryption type: "%s"'%enctype) + raise ValueError('Unsupported encryption type: "%s"'%enctype) return self.encrypt_payload_aes_cbc(payload, finalkey, iv) def encrypt_payload_aes_cbc(self, payload, finalkey, iv): @@ -138,10 +139,10 @@ def encrypt_payload_aes_cbc(self, payload, finalkey, iv): cipher = AES.new(finalkey, AES.MODE_CBC, iv) # pad out and store amount as last value length = len(payload) - encsize = (length/AES.block_size+1)*16 - padding = encsize - length + encsize = (length//AES.block_size+1)*16 + padding = int(encsize - length) for ind in range(padding): - payload += chr(padding) + payload += chr(padding).encode('ascii') return cipher.encrypt(payload) def __str__(self): @@ -152,7 +153,7 @@ def __str__(self): def encode_payload(self): 'Return encoded, plaintext groups+entries buffer' - payload = "" + payload = b"" for group in self.groups: payload += group.encode() for entry in self.entries: @@ -169,12 +170,12 @@ def write(self,filename,masterkey=""): Resets IVs and master seeds. ''' self.generate_contents_hash() - + header = copy(self.header) header.ngroups = len(self.groups) header.nentries = len(self.entries) header.reset_random_fields() - + finalkey = self.final_key(masterkey = masterkey or self.masterkey, masterseed = header.master_seed, masterseed2 = header.master_seed2, @@ -184,7 +185,7 @@ def write(self,filename,masterkey=""): header.encryption_type(), header.encryption_iv) - fp = open(filename,'w') + fp = open(filename,'wb') fp.write(header.encode()) fp.write(payload) fp.close() @@ -259,7 +260,7 @@ def gen_groupid(self): """ existing_groupids = {group.groupid for group in self.groups} if len(existing_groupids) >= 0xfffffffe: - six.reraise(Exception("All groupids are in use!")) + raise Exception("All groupids are in use!") while True: groupid = random.randint(1, 0xfffffffe) # 0 and 0xffffffff are reserved if groupid not in existing_groupids: From 601c6037eaeeefd8bdf3298cc5f9bef0df87f54b Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Thu, 2 Jan 2014 18:25:33 +0300 Subject: [PATCH 07/16] code pass the tests --- python/keepass/header.py | 4 +--- python/keepass/hier.py | 2 +- python/keepass/infoblock.py | 8 ++++---- python/keepass/kpdb.py | 13 ++++++++----- tests/test_de.py | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/python/keepass/header.py b/python/keepass/header.py index 8f95860..3337cd1 100644 --- a/python/keepass/header.py +++ b/python/keepass/header.py @@ -133,9 +133,7 @@ def decode(self,buf): name,nbytes,typecode = field string = buf[index:index+nbytes] index += nbytes - if six.PY3: - string = string.encode() - value = struct.unpack('<'+typecode, string)[0] + value = struct.unpack(b'<'+typecode, string)[0] self.__dict__[name] = value continue diff --git a/python/keepass/hier.py b/python/keepass/hier.py index 8ea7fc9..302a741 100644 --- a/python/keepass/hier.py +++ b/python/keepass/hier.py @@ -10,7 +10,7 @@ # Free Software Foundation; either version 2, or (at your option) any # later version. -from infoblock import GroupInfo +from keepass.infoblock import GroupInfo import six def path2list(path): diff --git a/python/keepass/infoblock.py b/python/keepass/infoblock.py index b1f6118..b0a38dd 100644 --- a/python/keepass/infoblock.py +++ b/python/keepass/infoblock.py @@ -24,11 +24,11 @@ def null_de(): return (lambda buf:None, lambda val:None) def shunt_de(): return (lambda buf:buf, lambda val:val) def ascii_de(): - return (lambda buf:b2a_hex(buf).replace('\0',''), + return (lambda buf:b2a_hex(buf).replace(b'\0',b''), lambda val:a2b_hex(val)+b'\0') def string_de(): - return (lambda buf: buf.replace('\0',''), lambda val: val+'\0') + return (lambda buf: buf.replace(b'\0',b''), lambda val: val+b'\0') def short_de(): return (lambda buf:struct.unpack(" Date: Thu, 2 Jan 2014 18:41:22 +0300 Subject: [PATCH 08/16] code pass reading, writing, creating, deliting and printing --- python/keepass/hier.py | 2 +- python/keepass/infoblock.py | 2 +- python/keepass/kpdb.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/keepass/hier.py b/python/keepass/hier.py index 302a741..8ea7fc9 100644 --- a/python/keepass/hier.py +++ b/python/keepass/hier.py @@ -10,7 +10,7 @@ # Free Software Foundation; either version 2, or (at your option) any # later version. -from keepass.infoblock import GroupInfo +from infoblock import GroupInfo import six def path2list(path): diff --git a/python/keepass/infoblock.py b/python/keepass/infoblock.py index b0a38dd..244b7de 100644 --- a/python/keepass/infoblock.py +++ b/python/keepass/infoblock.py @@ -71,7 +71,7 @@ def __init__(self,format,string=None): def __str__(self): ret = [self.__class__.__name__ + ':'] - for num,form in self.format.iteritems(): + for num,form in six.iteritems(self.format): try: value = self.__dict__[form[0]] except KeyError: diff --git a/python/keepass/kpdb.py b/python/keepass/kpdb.py index fb7f6e5..76490ae 100644 --- a/python/keepass/kpdb.py +++ b/python/keepass/kpdb.py @@ -26,9 +26,9 @@ from Crypto.Cipher import AES import hashlib -from keepass.header import DBHDR -from keepass.infoblock import GroupInfo, EntryInfo -from keepass import hier +from header import DBHDR +from infoblock import GroupInfo, EntryInfo +import hier class Database(object): ''' From 8632e0ca34f0170ac0996cc2ee272916df2d393b Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Thu, 2 Jan 2014 21:29:35 +0300 Subject: [PATCH 09/16] prepared for creating and installing keepass package --- README.md | 2 +- README.txt | 106 +++++++++++++++++++++++++++++++++++++ python/keepass/__init__.py | 11 ++++ python/keepass/cli.py | 2 +- python/keepass/hier.py | 2 +- python/keepass/kpdb.py | 6 +-- setup.py | 25 +++++++-- 7 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 README.txt diff --git a/README.md b/README.md index 2a976ba..60873a8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # keepassc and python-keepass -This provides command line and Python interfaces for operating on +This provides command line and Python (both 2 and 3) interfaces for operating on files in KeePass format v3 (used by [KeePass](http://keepass.info/) 1.x, and [KeePassX](http://www.keepassx.org/)). Note, this is not the format used by the KeePass application version 2.x. diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..60873a8 --- /dev/null +++ b/README.txt @@ -0,0 +1,106 @@ +# keepassc and python-keepass + +This provides command line and Python (both 2 and 3) interfaces for operating on +files in KeePass format v3 (used by [KeePass](http://keepass.info/) +1.x, and [KeePassX](http://www.keepassx.org/)). Note, this is not the +format used by the KeePass application version 2.x. + +## Notes of caution + +Before using this code, understand the its (known) security +and correctness limitations: + + * Unlike the KeePass/KeePassX GUI applications this code makes no + attempt to secure its memory. Input files read in are stored in + memory fully decrypted. + + * It is quite easy to display the stored passwords in plain text, + although the defaults try to avoid this. + + * Specifying the master key on the command line will leave traces in + your shells history and in the process list. + + * While input files are treated as read-only, keep backups of any + files written by KeePass/KeePassX until you are assured that files + written by this code are usable. + + * Key files are not currently supported. + +## Prerequisites and Installation + +You will need to install the python-crypto package (providing the +"Crypto" module). On a well behaved system do: + +```shell +sudo apt-get install python-crypto +``` + +If installing into a [virtualenv](http://www.virtualenv.org) this prerequisite will be installed for you: + +```shell +virtualenv /path/to/venv +source /path/to/venv/bin/activate +cd python-keepass +python setup.py install +``` + + +## Command line + +The command line interface is run like: + +```shell +keepassc [general_options] [command command_options] ... +``` + +Multiple commands can be specified and will be executed in order. +They operate on an in-memory instance of the database file. An +example, + +```shell +keepass open -m secret file.kdb \ + dump -p -f '%(username)s password is: %(password)s' \ + save -m newsecret backup.kdb +``` + +Online help: + +```shell +keepass -h # short usage +keepass help # full usage +``` + +## Python Modules + +### Low level file access + +```python +from keepass import kpdb +db = kpdb.Database(filename,masterkey) +print db # warning: displayed passwords in plaintext! +``` + +# References and Credits + +## PyCrypto help + + * Main page is found through . The documentation there is a start, but not enough. + * This blog post is useful for the basics: + +## The giants on whose shoulders this works stands + +First, thanks to the original authors, contributors and community +behind KeePass and KeePassX. I am merely a user of KeePassX. + +A big credit is due to rudi & shirou (same hacker?) for the following: + + * + * + +Looking through KeePass/KeePassX source made my head swim. Only after +reviewing their work could I get started. + +## License + +This package is Free Software licensed to you under the GPL v2 or +later at your discretion. See the [LICENSE.txt](LICENSE.txt) file for details. diff --git a/python/keepass/__init__.py b/python/keepass/__init__.py index 9fd3e88..dfccd92 100644 --- a/python/keepass/__init__.py +++ b/python/keepass/__init__.py @@ -12,3 +12,14 @@ # ... even thoough this file is essentially empty.... +__author__ = ["Brett Viren", "Jeremy Ehrhardt", "Filipp Frizzy"] +__copyright__ = "" +__credits__ = ["Brett Viren", "Jeremy Ehrhardt", "Filipp Frizzy"] +__license__ = "GPL" +__version__ = "1.0" +__maintainer__ = "Filipp Frizzy" +__email__ = "filipp.s.frizzy@gmail.com" +__status__ = "Development" +__description__ = "Python (both 2 and 3) interface and cli to KeePass file format v3 (used in KeePass V1.x and KeePassX)" +__keywords__ = "python keepass kdb" +__url__ = "https://github.com/Friz-zy/python-keepass" \ No newline at end of file diff --git a/python/keepass/cli.py b/python/keepass/cli.py index 39874b6..de6a013 100644 --- a/python/keepass/cli.py +++ b/python/keepass/cli.py @@ -14,7 +14,7 @@ import six from optparse import OptionParser import getpass -import kpdb +from keepass import kpdb class Cli(object): ''' diff --git a/python/keepass/hier.py b/python/keepass/hier.py index 8ea7fc9..302a741 100644 --- a/python/keepass/hier.py +++ b/python/keepass/hier.py @@ -10,7 +10,7 @@ # Free Software Foundation; either version 2, or (at your option) any # later version. -from infoblock import GroupInfo +from keepass.infoblock import GroupInfo import six def path2list(path): diff --git a/python/keepass/kpdb.py b/python/keepass/kpdb.py index 76490ae..fb7f6e5 100644 --- a/python/keepass/kpdb.py +++ b/python/keepass/kpdb.py @@ -26,9 +26,9 @@ from Crypto.Cipher import AES import hashlib -from header import DBHDR -from infoblock import GroupInfo, EntryInfo -import hier +from keepass.header import DBHDR +from keepass.infoblock import GroupInfo, EntryInfo +from keepass import hier class Database(object): ''' diff --git a/setup.py b/setup.py index 8a30ae0..6f5eed7 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,20 @@ from setuptools import setup +from os.path import join, dirname +import sys +sys.path.append("python") + +import keepass as module setup( - name ='python-keepass', - version='1.0', - description='Command line and Python interfaces for operating on files in KeePass .kdb format', + name ='keepass', + version = module.__version__, + author = module.__author__, + author_email = module.__email__, + description = module.__description__, + license = module.__license__, + keywords = module.__keywords__, + url = module.__url__, # project home page, if any + long_description=open(join(dirname(__file__), 'README.txt')).read(), package_dir={'': 'python'}, packages=['keepass'], scripts=[ @@ -11,6 +22,14 @@ ], install_requires=[ 'pycrypto', + 'six', + ], + classifiers=[ + 'License :: OSI Approved :: GPL License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', ], tests_require=[ 'nose', From 2c983eff2710646d1c5198fb8a48a5cde96741b3 Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Fri, 3 Jan 2014 19:58:15 +0300 Subject: [PATCH 10/16] prepared for creating and installing keepass package --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6f5eed7..a36ab1c 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ 'six', ], classifiers=[ - 'License :: OSI Approved :: GPL License', + 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', From 756ab42eebaa3a8b9d48f8cceb153b844b71843d Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Wed, 8 Jan 2014 03:53:16 +0300 Subject: [PATCH 11/16] chenge license from GPLv2 to GPLv2+ in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a36ab1c..afba732 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ 'six', ], classifiers=[ - 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', + 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', From 8e36c118fa27f11ef75166dc42805e14aab6a982 Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Wed, 8 Jan 2014 20:57:47 +0300 Subject: [PATCH 12/16] add crud test, add convertion to string for some of values in decode function on infoblock.py --- python/keepass/infoblock.py | 11 +++++++++++ python/keepass/test_crud.py | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100755 python/keepass/test_crud.py diff --git a/python/keepass/infoblock.py b/python/keepass/infoblock.py index 244b7de..11bd60e 100644 --- a/python/keepass/infoblock.py +++ b/python/keepass/infoblock.py @@ -96,6 +96,15 @@ def decode(self,string): if name is None: break try: value = decenc[0](buf) + if name in ['group_name', + 'title', + 'url', + 'username', + 'password', + 'notes', + 'binary_desc', + 'uuid']: + value = value.decode() except struct.error as msg: msg = '%s, typ = %d[%d] -> %s buf = "%s"'%\ (msg,typ,siz,self.format[typ],buf) @@ -122,6 +131,8 @@ def encode(self): value = self.__dict__[name] if six.PY3 and isinstance(value, six.string_types): value = bytes(value, 'utf-8') + elif isinstance(value, six.string_types): + value = str(value) encoded = decenc[1](value) pass buf = struct.pack(' Date: Wed, 8 Jan 2014 21:02:34 +0300 Subject: [PATCH 13/16] up version to 1.1 --- python/keepass/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/keepass/__init__.py b/python/keepass/__init__.py index dfccd92..4764ede 100644 --- a/python/keepass/__init__.py +++ b/python/keepass/__init__.py @@ -16,7 +16,7 @@ __copyright__ = "" __credits__ = ["Brett Viren", "Jeremy Ehrhardt", "Filipp Frizzy"] __license__ = "GPL" -__version__ = "1.0" +__version__ = "1.1" __maintainer__ = "Filipp Frizzy" __email__ = "filipp.s.frizzy@gmail.com" __status__ = "Development" From fead93e54aebcff1e3d3dd6da9f6417f2c9994e8 Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Wed, 8 Jan 2014 22:45:15 +0300 Subject: [PATCH 14/16] change license to GPLv2+ in __init__.py --- python/keepass/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/keepass/__init__.py b/python/keepass/__init__.py index 4764ede..a6debcb 100644 --- a/python/keepass/__init__.py +++ b/python/keepass/__init__.py @@ -15,7 +15,7 @@ __author__ = ["Brett Viren", "Jeremy Ehrhardt", "Filipp Frizzy"] __copyright__ = "" __credits__ = ["Brett Viren", "Jeremy Ehrhardt", "Filipp Frizzy"] -__license__ = "GPL" +__license__ = "GPLv2+" __version__ = "1.1" __maintainer__ = "Filipp Frizzy" __email__ = "filipp.s.frizzy@gmail.com" From 6467639c209f88e4e29b240d0135145f598c90bd Mon Sep 17 00:00:00 2001 From: Benjamin Hedrich Date: Sun, 16 Mar 2014 17:22:17 +0100 Subject: [PATCH 15/16] Add Python 2.6.x compatibility --- python/keepass/kpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/keepass/kpdb.py b/python/keepass/kpdb.py index fb7f6e5..eda6bed 100644 --- a/python/keepass/kpdb.py +++ b/python/keepass/kpdb.py @@ -261,7 +261,7 @@ def gen_groupid(self): """ Generate a new groupid (4-byte value that isn't 0 or 0xffffffff). """ - existing_groupids = {group.groupid for group in self.groups} + existing_groupids = set(group.groupid for group in self.groups) if len(existing_groupids) >= 0xfffffffe: raise Exception("All groupids are in use!") while True: From 10c6a9613e7d9701ed3faa0778fecc7e15d15aba Mon Sep 17 00:00:00 2001 From: Filipp Frizzy Date: Mon, 24 Mar 2014 22:04:35 +0300 Subject: [PATCH 16/16] up version to 1.2 --- python/keepass/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/keepass/__init__.py b/python/keepass/__init__.py index a6debcb..75cc2ba 100644 --- a/python/keepass/__init__.py +++ b/python/keepass/__init__.py @@ -16,10 +16,10 @@ __copyright__ = "" __credits__ = ["Brett Viren", "Jeremy Ehrhardt", "Filipp Frizzy"] __license__ = "GPLv2+" -__version__ = "1.1" +__version__ = "1.2" __maintainer__ = "Filipp Frizzy" __email__ = "filipp.s.frizzy@gmail.com" __status__ = "Development" __description__ = "Python (both 2 and 3) interface and cli to KeePass file format v3 (used in KeePass V1.x and KeePassX)" __keywords__ = "python keepass kdb" -__url__ = "https://github.com/Friz-zy/python-keepass" \ No newline at end of file +__url__ = "https://github.com/Friz-zy/python-keepass"