Browse Source

Co-existing hashed and non-hashed password

This set of changes allows co-existing of both types
of keys in code. The next step would be to silently migrate
between the two.
Co-existing hashed and non-hashed password

This set of changes allows co-existing of both types
of keys in code. The next step would be to silently migrate
between the two.
oz123 11 years ago
parent
commit
47967c73e1

+ 4 - 1
pwman/__init__.py

@@ -25,8 +25,11 @@ from util import config
 import sys
 import sys
 import re
 import re
 import data.factory
 import data.factory
+from pwman.data.database import __DB_FORMAT__
 
 
 appname = "pwman3"
 appname = "pwman3"
+
+
 try:
 try:
     version = pkg_resources.get_distribution('pwman3').version
     version = pkg_resources.get_distribution('pwman3').version
 except pkg_resources.DistributionNotFound:  # pragma: no cover
 except pkg_resources.DistributionNotFound:  # pragma: no cover
@@ -183,5 +186,5 @@ def get_db_version(config, dbtype, args):
         if dbver < 0.4 and not args.dbconvert:
         if dbver < 0.4 and not args.dbconvert:
             print(_db_warn)
             print(_db_warn)
     else:
     else:
-        dbver = 0.4
+        dbver = __DB_FORMAT__
     return dbver
     return dbver

+ 1 - 0
pwman/data/database.py

@@ -21,6 +21,7 @@
 
 
 from pwman.util.crypto import CryptoEngine
 from pwman.util.crypto import CryptoEngine
 
 
+__DB_FORMAT__ = 0.5
 
 
 class DatabaseException(Exception):
 class DatabaseException(Exception):
     pass  # prage: no cover
     pass  # prage: no cover

+ 11 - 8
pwman/data/drivers/sqlite.py

@@ -22,6 +22,7 @@
 
 
 """SQLite Database implementation."""
 """SQLite Database implementation."""
 from pwman.data.database import Database, DatabaseException
 from pwman.data.database import Database, DatabaseException
+from pwman.data.database import __DB_FORMAT__
 from pwman.data.nodes import NewNode
 from pwman.data.nodes import NewNode
 from pwman.util.crypto import CryptoEngine
 from pwman.util.crypto import CryptoEngine
 import sqlite3 as sqlite
 import sqlite3 as sqlite
@@ -302,21 +303,23 @@ class SQLiteDatabaseNewForm(Database):
             # SQLite does have constraints implemented at the moment
             # SQLite does have constraints implemented at the moment
             # so datatype will just be a string
             # so datatype will just be a string
             self._cur.execute("CREATE TABLE NODES (ID INTEGER PRIMARY KEY"
             self._cur.execute("CREATE TABLE NODES (ID INTEGER PRIMARY KEY"
-                              + " AUTOINCREMENT,DATA BLOB NOT NULL)")
+                              " AUTOINCREMENT,DATA BLOB NOT NULL)")
             self._cur.execute("CREATE TABLE TAGS"
             self._cur.execute("CREATE TABLE TAGS"
-                              + "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
-                              + "DATA BLOB NOT NULL UNIQUE)")
+                              "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
+                              "DATA BLOB NOT NULL UNIQUE)")
             self._cur.execute("CREATE TABLE LOOKUP"
             self._cur.execute("CREATE TABLE LOOKUP"
-                              + "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
-                              + " PRIMARY KEY(NODE, TAG))")
+                              "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
+                              " PRIMARY KEY(NODE, TAG))")
 
 
             self._cur.execute("CREATE TABLE KEY"
             self._cur.execute("CREATE TABLE KEY"
-                              + "(THEKEY TEXT NOT NULL DEFAULT '')")
+                              "(THEKEY TEXT NOT NULL DEFAULT '')")
             self._cur.execute("INSERT INTO KEY VALUES('')")
             self._cur.execute("INSERT INTO KEY VALUES('')")
             # create a table to hold DB version info
             # create a table to hold DB version info
             self._cur.execute("CREATE TABLE DBVERSION"
             self._cur.execute("CREATE TABLE DBVERSION"
-                              + "(DBVERSION TEXT NOT NULL DEFAULT '0.4')")
-            self._cur.execute("INSERT INTO DBVERSION VALUES('0.4')")
+                              "(DBVERSION TEXT NOT NULL DEFAULT '%s')" %
+                              __DB_FORMAT__)
+            self._cur.execute("INSERT INTO DBVERSION VALUES('%s')" %
+                              __DB_FORMAT__ )
             try:
             try:
                 self._con.commit()
                 self._con.commit()
             except DatabaseException as e:  # pragma: no cover
             except DatabaseException as e:  # pragma: no cover

+ 2 - 2
pwman/data/factory.py

@@ -53,9 +53,9 @@ def create(dbtype, version=None, filename=None):
     """
     """
     if dbtype == "SQLite":
     if dbtype == "SQLite":
         from pwman.data.drivers import sqlite
         from pwman.data.drivers import sqlite
-        if version == 0.4 and filename:
+        if version >= 0.4 and filename:
             db = sqlite.SQLiteDatabaseNewForm(filename)
             db = sqlite.SQLiteDatabaseNewForm(filename)
-        elif version == 0.4:
+        elif version >= 0.4:
             db = sqlite.SQLiteDatabaseNewForm()
             db = sqlite.SQLiteDatabaseNewForm()
         else:
         else:
             db = None
             db = None

+ 1 - 1
pwman/tests/db_tests.py

@@ -380,7 +380,7 @@ class CLITests(unittest.TestCase):
 class FactoryTest(unittest.TestCase):
 class FactoryTest(unittest.TestCase):
 
 
     def test_factory_check_db_ver(self):
     def test_factory_check_db_ver(self):
-        self.assertEquals(factory.check_db_version('SQLite'), 0.4)
+        self.assertEquals(factory.check_db_version('SQLite'), 0.5)
 
 
 
 
 class ConfigTest(unittest.TestCase):
 class ConfigTest(unittest.TestCase):

+ 67 - 6
pwman/util/crypto.py

@@ -45,7 +45,6 @@ plaintext = cyypto.decrypt(ciphertext)
 from Crypto.Cipher import Blowfish as cBlowfish
 from Crypto.Cipher import Blowfish as cBlowfish
 from Crypto.Cipher import AES as cAES
 from Crypto.Cipher import AES as cAES
 from Crypto.Cipher import ARC2 as cARC2
 from Crypto.Cipher import ARC2 as cARC2
-from Crypto.Cipher import ARC4 as cARC4
 from Crypto.Cipher import CAST as cCAST
 from Crypto.Cipher import CAST as cCAST
 from Crypto.Cipher import DES as cDES
 from Crypto.Cipher import DES as cDES
 from Crypto.Cipher import DES3 as cDES3
 from Crypto.Cipher import DES3 as cDES3
@@ -115,13 +114,25 @@ class CryptoPasswordMismatchException(CryptoException):
 
 
 
 
 class CryptoEngine(object):
 class CryptoEngine(object):
-    """Cryptographic Engine"""
+    """
+    Cryptographic Engine, overrides CryptoEngineOld.
+    The main change is that _getcipher_real is now hashing the key
+    before encrypting it.
+
+    This method can eventually remove the call to _retrievedata,
+    which used to strip the _TAG from the plain text string or return
+    the cPickle object as string.
+    Since we don't use cPickle to serialize object anymore, we can
+    safely aim towards removing this method. Thus, removing also
+    the _TAG in the beginning of each string as per recommendation of
+    Ralf Herzog.
+    """
     _timeoutcount = 0
     _timeoutcount = 0
     _instance = None
     _instance = None
     _callback = None
     _callback = None
 
 
     @classmethod
     @classmethod
-    def get(cls):
+    def get(cls, dbver=None):
         """
         """
         CryptoEngine.get() -> CryptoEngine
         CryptoEngine.get() -> CryptoEngine
         Return an instance of CryptoEngine.
         Return an instance of CryptoEngine.
@@ -131,8 +142,11 @@ class CryptoEngine(object):
             algo = config.get_value("Encryption", "algorithm")
             algo = config.get_value("Encryption", "algorithm")
             if algo == "Dummy":
             if algo == "Dummy":
                 CryptoEngine._instance = DummyCryptoEngine()
                 CryptoEngine._instance = DummyCryptoEngine()
-            else:
+            elif dbver < 0.5:
+                CryptoEngine._instance = CryptoEngineOld()
+            elif dbver == 0.5:
                 CryptoEngine._instance = CryptoEngine()
                 CryptoEngine._instance = CryptoEngine()
+
         return CryptoEngine._instance
         return CryptoEngine._instance
 
 
     def __init__(self):
     def __init__(self):
@@ -180,7 +194,6 @@ class CryptoEngine(object):
         key = str(key).decode('base64')
         key = str(key).decode('base64')
         self._cipher = self._getcipher_real(key, self._algo)
         self._cipher = self._getcipher_real(key, self._algo)
 
 
-
     def encrypt(self, obj):
     def encrypt(self, obj):
         """
         """
         encrypt(obj) -> ciphertext
         encrypt(obj) -> ciphertext
@@ -375,7 +388,7 @@ password again")
         """
         """
         prepare data before encrypting
         prepare data before encrypting
         """
         """
-        #plaintext = cPickle.dumps(obj)
+        # plaintext = cPickle.dumps(obj)
         plaintext = _TAG + obj
         plaintext = _TAG + obj
         numblocks = (len(plaintext)/blocksize) + 1
         numblocks = (len(plaintext)/blocksize) + 1
         newdatasize = blocksize*numblocks
         newdatasize = blocksize*numblocks
@@ -419,3 +432,51 @@ class DummyCryptoEngine(CryptoEngine):
 
 
     def changepassword(self):
     def changepassword(self):
         return ''
         return ''
+
+
+class CryptoEngineOld(CryptoEngine):
+
+    def _getcipher_real(self, key, algo):
+        """
+        do the real job of decrypting using functions
+        form PyCrypto
+        """
+        if (algo == "AES"):
+            key = self._padkey(key, [16, 24, 32])
+            cipher = cAES.new(key, cAES.MODE_ECB)
+        elif (algo == 'ARC2'):
+            cipher = cARC2.new(key, cARC2.MODE_ECB)
+        elif (algo == 'ARC4'):
+            raise CryptoUnsupportedException("ARC4 is currently unsupported")
+        elif (algo == 'Blowfish'):
+            cipher = cBlowfish.new(key, cBlowfish.MODE_ECB)
+        elif (algo == 'CAST'):
+            cipher = cCAST.new(key, cCAST.MODE_ECB)
+        elif (algo == 'DES'):
+            self._padkey(key, [8])
+            cipher = cDES.new(key, cDES.MODE_ECB)
+        elif (algo == 'DES3'):
+            key = self._padkey(key, [16, 24])
+            cipher = cDES3.new(key, cDES3.MODE_ECB)
+        elif (algo == 'XOR'):
+            raise CryptoUnsupportedException("XOR is currently unsupported")
+        else:
+            raise CryptoException("Invalid algorithm specified")
+        return cipher
+
+    def _padkey(self, key, acceptable_lengths):
+        """
+        pad key with extra string
+        """
+        maxlen = max(acceptable_lengths)
+        keylen = len(key)
+        if (keylen > maxlen):
+            return key[0:maxlen]
+        acceptable_lengths.sort()
+        acceptable_lengths.reverse()
+        newkeylen = None
+        for i in acceptable_lengths:
+            if (i < keylen):
+                break
+            newkeylen = i
+        return key.ljust(newkeylen)

+ 1 - 2
scripts/pwman3

@@ -20,7 +20,6 @@
 # Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
 # Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
 #============================================================================
 #============================================================================
 from __future__ import print_function
 from __future__ import print_function
-import os
 import sys
 import sys
 import shutil
 import shutil
 from pwman import get_conf_options, get_db_version
 from pwman import get_conf_options, get_db_version
@@ -70,8 +69,8 @@ def auto_convert():
 def main(args):
 def main(args):
     PwmanCliNew, OSX = get_ui_platform(sys.platform)
     PwmanCliNew, OSX = get_ui_platform(sys.platform)
     xselpath, dbtype = get_conf_options(args, OSX)
     xselpath, dbtype = get_conf_options(args, OSX)
-    enc = CryptoEngine.get()
     dbver = get_db_version(config, dbtype, args)
     dbver = get_db_version(config, dbtype, args)
+    enc = CryptoEngine.get(dbver)
 
 
     if args.dbconvert:
     if args.dbconvert:
         dbconvertor = PwmanConvertDB(args, config)
         dbconvertor = PwmanConvertDB(args, config)