Sfoglia il codice sorgente

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 10 anni fa
parent
commit
47967c73e1

+ 4 - 1
pwman/__init__.py

@@ -25,8 +25,11 @@ from util import config
 import sys
 import re
 import data.factory
+from pwman.data.database import __DB_FORMAT__
 
 appname = "pwman3"
+
+
 try:
     version = pkg_resources.get_distribution('pwman3').version
 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:
             print(_db_warn)
     else:
-        dbver = 0.4
+        dbver = __DB_FORMAT__
     return dbver

+ 1 - 0
pwman/data/database.py

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

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

@@ -22,6 +22,7 @@
 
 """SQLite Database implementation."""
 from pwman.data.database import Database, DatabaseException
+from pwman.data.database import __DB_FORMAT__
 from pwman.data.nodes import NewNode
 from pwman.util.crypto import CryptoEngine
 import sqlite3 as sqlite
@@ -302,21 +303,23 @@ class SQLiteDatabaseNewForm(Database):
             # SQLite does have constraints implemented at the moment
             # so datatype will just be a string
             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"
-                              + "(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"
-                              + "(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"
-                              + "(THEKEY TEXT NOT NULL DEFAULT '')")
+                              "(THEKEY TEXT NOT NULL DEFAULT '')")
             self._cur.execute("INSERT INTO KEY VALUES('')")
             # create a table to hold DB version info
             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:
                 self._con.commit()
             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":
         from pwman.data.drivers import sqlite
-        if version == 0.4 and filename:
+        if version >= 0.4 and filename:
             db = sqlite.SQLiteDatabaseNewForm(filename)
-        elif version == 0.4:
+        elif version >= 0.4:
             db = sqlite.SQLiteDatabaseNewForm()
         else:
             db = None

+ 1 - 1
pwman/tests/db_tests.py

@@ -380,7 +380,7 @@ class CLITests(unittest.TestCase):
 class FactoryTest(unittest.TestCase):
 
     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):

+ 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 AES as cAES
 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 DES as cDES
 from Crypto.Cipher import DES3 as cDES3
@@ -115,13 +114,25 @@ class CryptoPasswordMismatchException(CryptoException):
 
 
 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
     _instance = None
     _callback = None
 
     @classmethod
-    def get(cls):
+    def get(cls, dbver=None):
         """
         CryptoEngine.get() -> CryptoEngine
         Return an instance of CryptoEngine.
@@ -131,8 +142,11 @@ class CryptoEngine(object):
             algo = config.get_value("Encryption", "algorithm")
             if algo == "Dummy":
                 CryptoEngine._instance = DummyCryptoEngine()
-            else:
+            elif dbver < 0.5:
+                CryptoEngine._instance = CryptoEngineOld()
+            elif dbver == 0.5:
                 CryptoEngine._instance = CryptoEngine()
+
         return CryptoEngine._instance
 
     def __init__(self):
@@ -180,7 +194,6 @@ class CryptoEngine(object):
         key = str(key).decode('base64')
         self._cipher = self._getcipher_real(key, self._algo)
 
-
     def encrypt(self, obj):
         """
         encrypt(obj) -> ciphertext
@@ -375,7 +388,7 @@ password again")
         """
         prepare data before encrypting
         """
-        #plaintext = cPickle.dumps(obj)
+        # plaintext = cPickle.dumps(obj)
         plaintext = _TAG + obj
         numblocks = (len(plaintext)/blocksize) + 1
         newdatasize = blocksize*numblocks
@@ -419,3 +432,51 @@ class DummyCryptoEngine(CryptoEngine):
 
     def changepassword(self):
         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>
 #============================================================================
 from __future__ import print_function
-import os
 import sys
 import shutil
 from pwman import get_conf_options, get_db_version
@@ -70,8 +69,8 @@ def auto_convert():
 def main(args):
     PwmanCliNew, OSX = get_ui_platform(sys.platform)
     xselpath, dbtype = get_conf_options(args, OSX)
-    enc = CryptoEngine.get()
     dbver = get_db_version(config, dbtype, args)
+    enc = CryptoEngine.get(dbver)
 
     if args.dbconvert:
         dbconvertor = PwmanConvertDB(args, config)