Browse Source

Merge branch 'release-0.9'

Oz N Tiram 8 years ago
parent
commit
97bb86e3ac

+ 1 - 0
.gitignore

@@ -15,3 +15,4 @@ htmlcov/*
 secret.txt
 .tox
 *~
+.vagrant*

+ 0 - 1
.travis.yml

@@ -1,6 +1,5 @@
 language: python
 python:
-  - 2.7
   - 3.4 
   - 3.5
 

+ 6 - 0
ChangeLog

@@ -1,3 +1,9 @@
+2016-August-09 Oz Nahum Tiram <oz.tiram@gmail.com>
+	* Release 0.9.0	
+	* Completely drop Python2 support
+	* Migration from PyCrypto to Cryptography completed
+	  Note that this breaks compatablity with earlier versions.
+
 2016-July-16 Oz Nahum Tiram <oz.tiram@gmail.com>
 	* Release 0.8.1 - Tag only, not on pypi
 	* Fixes of Python2 - Python3 compatablity issues

+ 0 - 13
docs/general_notes.txt

@@ -65,20 +65,7 @@ finally you should step into:
 # upload  
   
   twine upload dist/pwman.tar.gz
-
-# PyCrypto Alternatives:
-
-  * http://wiki.yobi.be/wiki/PyCryptoPlus#Differences_with_pycrypto
-  *  http://brandon.sternefamily.net/2007/06/aes-tutorial-python-implementation/
-  * puresalsa20 - not document so good
-  * http://code.google.com/p/pycrypt/
-  * https://github.com/trevp/tlslite/tree/master/tlslite/utils
-  * seems like the best next standard, but very young:
-    https://github.com/alex/cryptography
-  * another salsa Alternative: www.seanet.com/~bugbee/crypto/salsa20/salsa20.pyb
    
-# for windows port: https://pypi.python.org/pypi/colorama
-
 # db ... https://github.com/coleifer/peewee
          http://sqlobject.org/
          http://www.sqlalchemy.org/

+ 43 - 33
docs/source/configuration.rst

@@ -31,43 +31,53 @@ The following is an example default config file::
     [Database]
     filename = sqlite:///<PWMAN_CONFIG>/pwman.db`
     
+    [Updater]
+    supress_version_check = no
+    client_info = ee5cd64310568736b971e3fb7c7064a4459b99a2b78672515fd0f06c82f65d5
+
 
 Following is a table describing the parameters and their meanings:
 
 
-    ===========   ===========
-    **Section**   *Readline* 
-    -----------   -----------
-                  *Global*
-    history       path to the file containing history of commands typed
-    -----------   -----------
-    **Section**   *Global* 
-    -----------   -----------
-    save          True or False - whether the Configuring file should be saved
-    -----------   -----------
-    colors        yes or no - If set to *no*, no colors used in output. This is useful for breil terminals. 
-    -----------   -----------
-    cp_timeout    Number of seconds before the clipboard is erased.
-    -----------   -----------
-    cls_timeout   Number of seconds before the screen is clean after a print.
-    -----------   -----------
-    umask         The umask in which database and configuration files are written.
-    -----------   -----------
-    xsel          path to the xsel binary (Linux\BSD only) 
-    -----------   -----------
-    **Section**   *Database* 
-    -----------   -----------
-    dburi         Database URI conforming to `RFC3986`_. SQLite, Postgreql, 
-                  MySQL and MongoDB are currently supported. 
-       
-                  SQLite example: `sqlite:///path/to/your/db`
-
-                  Postgreql example: `postgresql://<user>:<pass>@<host[:port]>/<database>`
-
-                  MySQL example:     `mysql://<user>:<pass>@<host[:port]>/<database>`
-                  
-                  MongoDB example:   `mongodb://<user>:<pass>@<host[:port]>/<database>`
-    ===========   ===========
+    =====================    ===========
+    **Section**              *Readline* 
+    ---------------------    -----------
+                             *Global*
+    history                  path to the file containing history of commands typed
+    ---------------------    -----------
+    **Section**              *Global* 
+    ---------------------    -----------
+    save                     True or False - whether the Configuring file should be saved
+    ---------------------    -----------
+    colors                   yes or no - If set to *no*, no colors used in output. This is useful for breil terminals. 
+    ---------------------    -----------
+    cp_timeout               Number of seconds before the clipboard is erased.
+    ---------------------    -----------
+    cls_timeout              Number of seconds before the screen is clean after a print.
+    ---------------------    -----------
+    umask                    The umask in which database and configuration files are written.
+    ---------------------    -----------
+    xsel                     path to the xsel binary (Linux\BSD only) 
+    ---------------------    -----------
+    **Section**              *Database* 
+    ---------------------    -----------
+    dburi                    Database URI conforming to `RFC3986`_. SQLite, Postgreql, 
+                             MySQL and MongoDB are currently supported. 
+      
+                             SQLite example: `sqlite:///path/to/your/db`
+
+                             Postgreql example: `postgresql://<user>:<pass>@<host[:port]>/<database>`
+
+                             MySQL example:     `mysql://<user>:<pass>@<host[:port]>/<database>`
+                 
+                             MongoDB example:   `mongodb://<user>:<pass>@<host[:port]>/<database>`
+    ---------------------    -----------
+    **Section**              *Updater*
+    ---------------------    -----------
+    supress_version_check    yes or no - check for newer versions of pwman3
+    ---------------------    -----------
+    client_info              sha256 digest of host name and username, used for identifying the client
+    =====================    ===========
 
 
 .. _RFC3986: http://www.ietf.org/rfc/rfc3986.txt

+ 1 - 1
docs/source/index.rst

@@ -19,7 +19,7 @@ Contents:
 About pwman3:
 ^^^^^^^^^^^^^ 
 
-Pwman3 is a command line password manager for Python 2.7-3.X with support for 
+Pwman3 is a command line password manager for Python3 with support for 
 multiple databases.
 
 

+ 33 - 8
docs/source/install.rst

@@ -13,6 +13,7 @@ Or you can install pwman3 cloning the repository and running::
 Inside the extraced directory. At the moment pypi still does not have 
 the latest version of pwman3.
 
+
 Upgrading from version 0.5.x
 ----------------------------
 
@@ -27,8 +28,9 @@ Once exported you should rename your old database, to keep a backup of it.
 Then you can install pwman3 in version 0.6.x or later as described above. When finished
 you can import your passwords from the CSV to a new database with::
 
-    $ pwman3 -i path_to_your_file.csv
+    $ pwman3 -i path_to_your_file.csv \;
 
+The `\;` tells the importer that the file is semi-colon separated.
 When the import is done, start pwman3 with::
     
     $ pwman3 
@@ -36,16 +38,36 @@ When the import is done, start pwman3 with::
 If the import was success, erase the CSV file, which contains your passwords 
 in clear text.
 
+Upgrading from version 0.6 or later:
+------------------------------------
+
+Once again the latest release (version 0.9) breaks compatibility with earlier
+versions. As a user you might consider this an annoyance, which is understandable.
+However, older Pwman3 version used a very old cryptography library which is
+no longer actively maintained. With the migration of the code base to use
+the cryptography_ library, there was a necessary change of the underlying
+encryption algorithm. AES encryption with ECB mode was the old algorithm used, 
+which is by now considered outdated, and it was replaced with Fernet encryption
+which is considered best practice.
+
+To upgrade, follow the path described above to export your database to a CSV,
+and the import it.
+
 A Note about Python versions
 ----------------------------
 
-Pwman3 was tested on Python versions 2.7-3.x. However, du to subtle differences
-in PyCrypto, unicode and other stuff it is not recommended to use the same database
-with different Python versions. 
-Hence, if you are using Python version 2.7.x to run Pwman3 and later on you would 
-like to change your default Python interpreter to Python 3 serious, it is recommended
-that you export your database and re-import it to a new database created using Python 
-3.X . 
+Earlier versions of Pwman3 supported both Python2 and Python3 versions. This
+created a bit of an effort for maintaining version compatablity, and served
+as a migration path for future versions. Python 3 has been around now for quite
+a while, and soon enough, Python2 support will end. Python 3 is mature enough, 
+and offers many improvements for developers. Python 3.4 is included in all major
+modern Linux distributions.
+If you use a certain enterprise Linux versions which does not ship Python 3.3
+or later, the process for installing a newer Python versions is pretty straight
+forward and very well documented. You should opt for using newer Python versions
+for all your software if possible.
+
+If you want to learn more about why Python 3, see the following `Python3 statement`_
 
 Database versions 
 ----------------- 
@@ -58,3 +80,6 @@ The required python drivers are:
  * pymysql  version 0.6.6 
  * psycopg2 version 2.6
  * pymongo version 2.8
+
+.. _cryptography: https://cryptography.io
+.. _Python3 statement: https://python3statement.github.io/

+ 3 - 3
docs/source/tutorial.rst

@@ -42,7 +42,7 @@ Most commands have a single or two letter alias which is easy to remember.
 
 As for a start you would probably like to store some passwords in your database, all 
 you need to do is type the command ``new`` (or the alias ``n``), and then insert the information
-as promted::
+as prompted::
 
     pwman> new
     Username: oz123
@@ -97,11 +97,11 @@ To see the content of a saved entry, you need to use the ``print`` command::
 
 Notice, that the ``print`` command expects an entry number as an argument. 
 After printing the content of the entry, pwman3 will wait for ``Enter`` to be 
-pressed or 5 seconds until it flushes the screen. This way, unautorized eyes 
+pressed or 5 seconds until it flushes the screen. This way, unauthorized eyes 
 can not browse your screen and see your password. You can always scroll up to 
 see the printed entry or print it again. 
 
-If you don't want to type passwords and urls constantly ``Pwman3`` comes with 
+If you don't want to type passwords and URLs constantly ``Pwman3`` comes with 
 two shortcut commands. The first shortcut is ``open``, when calling it with 
 an entry number it will open the URL in your default browser::
     

+ 44 - 18
pwman/__init__.py

@@ -18,27 +18,36 @@
 # ============================================================================
 # Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
 # ============================================================================
-import os
 import argparse
+import http.client
+import os
+import pkg_resources
 import re
 import string
-import pkg_resources
+import sys
 from pwman.util import config
 from pwman.data.factory import check_db_version
 
+try:
+    import cryptography  # noqa
+    has_cryptography = True
+except ImportError:
+    has_cryptography = False
+
+
 appname = "pwman3"
 
 try:
     version = pkg_resources.get_distribution('pwman3').version
 except pkg_resources.DistributionNotFound:  # pragma: no cover
-        version = "0.8.1"
+    version = "0.9.0"
 
 
 class PkgMetadata(object):
 
     def __init__(self):
         p = pkg_resources.get_distribution('pwman3')
-        f = open(os.path.join(p.location+'-info','PKG-INFO'))
+        f = open(os.path.join(p.location+'-info', 'PKG-INFO'))
         lines = f.readlines()
         self.summary = lines[3].split(':')[-1].strip()
         self.description = ''.join(map(string.strip, lines[9:14]))
@@ -55,7 +64,7 @@ try:
     long_description = pkg_meta.description
 except IOError as E:
     # this should only happen once when installing the package
-    description = "a command line password manager with support for multiple databases."
+    description = "a command line password manager with support for multiple databases."  # noqa
     website = 'http://pwman3.github.io/pwman3/'
 
 
@@ -73,16 +82,15 @@ config_dir = os.path.expanduser("~/.pwman")
 
 
 def parser_options(formatter_class=argparse.HelpFormatter):  # pragma: no cover
-    parser = argparse.ArgumentParser(
-            prog='pwman3',
-            description=description,
-            formatter_class=formatter_class)
+    parser = argparse.ArgumentParser(prog='pwman3',
+                                     description=description,
+                                     formatter_class=formatter_class)
     parser.add_argument('-c', '--config', dest='cfile',
                         default=os.path.expanduser("~/.pwman/config"),
                         help='cofiguration file to read')
     parser.add_argument('-d', '--database', dest='dbase')
     parser.add_argument('-i', '--import', nargs=2, dest='file_delim',
-            help="Specify the file name and the delimeter type")
+                        help="Specify the file name and the delimeter type")
     return parser
 
 
@@ -105,13 +113,6 @@ def set_xsel(configp, OSX):
         configp.set_value("Global", "xsel", pbcopypath)
 
 
-#def set_win_colors(config):  # pragma: no cover
-#    try:
-#        if sys.platform.startswith('win'):
-#            colorama.init()
-#   except ImportError:  # when installing colorama is still not there
-#        pass
-
 def set_umask(configp):
     umask = configp.get_value("Global", "umask")
     if re.search(r'^\d{4}$', umask):
@@ -130,7 +131,6 @@ def get_conf_options(args, OSX):
     if not xselpath:  # pragma: no cover
         set_xsel(configp, OSX)
 
-    #set_win_colors(configp)
     set_db(args, configp)
     set_umask(configp)
     dburi = configp.get_value("Database", "dburi")
@@ -140,3 +140,29 @@ def get_conf_options(args, OSX):
 def get_db_version(config, args):
     dburi = check_db_version(config.get_value("Database", "dburi"))
     return dburi
+
+
+def calculate_client_info():  # pragma: no cover
+    import hashlib
+    import socket
+    from getpass import getuser
+    hashinfo = hashlib.sha256((socket.gethostname() + getuser()).encode())
+    hashinfo = hashinfo.hexdigest()
+    return hashinfo
+
+
+def is_latest_version(version, client_info):  # pragma: no cover
+    """check current version againt latest version"""
+    try:
+        conn = http.client.HTTPConnection("pwman.tiram.it", timeout=0.5)
+        conn.request("GET",
+                     "/is_latest/?current_version={}&os={}&hash={}".format(
+                         version, sys.platform, client_info))
+        r = conn.getresponse()
+        data = r.read()  # This will return entire content.
+        if data.decode().split(".") > version.split("."):
+            return None, False
+        else:
+            return None, True
+    except Exception as E:
+        return E, True

+ 41 - 21
pwman/data/database.py

@@ -50,11 +50,11 @@ class Database(object):
             self._cur.execute("SELECT 1 from DBVERSION")
             version = self._cur.fetchone()
             return version
-        except self.ProgrammingError:
+        except Exception:
             self._con.rollback()
+            return 0
 
     def _create_tables(self):
-
         if self._check_tables():
             return
         try:
@@ -67,7 +67,7 @@ class Database(object):
 
             self._cur.execute("CREATE TABLE TAG"
                               "(ID  SERIAL PRIMARY KEY,"
-                              "DATA VARCHAR(255) NOT NULL UNIQUE)")
+                              "DATA TEXT NOT NULL)")
 
             self._cur.execute("CREATE TABLE LOOKUP ("
                               "nodeid INTEGER NOT NULL REFERENCES NODE(ID),"
@@ -84,7 +84,7 @@ class Database(object):
                               (self.dbversion,))
 
             self._con.commit()
-        except self.ProgrammingError:  # pragma: no cover
+        except Exception:  # pragma: no cover
             self._con.rollback()
 
     def get_user_password(self):
@@ -120,17 +120,30 @@ class Database(object):
             self._update_tag_lookup(nodeid, tid)
 
     def _get_tag(self, tagcipher):
-        sql_search = "SELECT ID FROM TAG WHERE DATA = {}".format(self._sub)
-        self._cur.execute(sql_search, ([tagcipher]))
-        rv = self._cur.fetchone()
-        return rv
+        sql_search = "SELECT * FROM TAG"
+        self._cur.execute(sql_search)
+        ce = CryptoEngine.get()
+
+        try:
+            tag = ce.decrypt(tagcipher)
+            encrypted = True
+        except Exception:
+            tag = tagcipher
+            encrypted = False
+
+        rv = self._cur.fetchall()
+        for idx, cipher in rv:
+            if encrypted and tag == ce.decrypt(cipher):
+                return idx
+            elif tag == cipher:
+                return idx
 
     def _get_or_create_tag(self, tagcipher):
         rv = self._get_tag(tagcipher)
         if rv:
-            return rv[0]
+            return rv
         else:
-            self._cur.execute(self._insert_tag_sql, ([tagcipher]))
+            self._cur.execute(self._insert_tag_sql, list(map(self._data_wrapper, (tagcipher,))))  # noqa
             try:
                 return self._cur.fetchone()[0]
             except TypeError:
@@ -150,9 +163,14 @@ class Database(object):
             sql = "SELECT * FROM NODE"
         self._cur.execute(sql, (ids))
         nodes = self._cur.fetchall()
+        if not nodes:
+            return []
+        # sqlite returns nodes as bytes, postgresql returns them as str
+        if isinstance(nodes[0][1], str):
+            nodes = [node for node in nodes]
         nodes_w_tags = []
         for node in nodes:
-            tags = list(self._get_node_tags(node))
+            tags = [t for t in self._get_node_tags(node)]
             nodes_w_tags.append(list(node) + tags)
 
         return nodes_w_tags
@@ -169,7 +187,8 @@ class Database(object):
             if not tagid:
                 return []  # pragma: no cover
 
-            self._cur.execute(self._list_nodes_sql, (tagid))
+            # will this work for many nodes??? with the same tag?
+            self._cur.execute(self._list_nodes_sql, (tagid,))
             self._con.commit()
             ids = self._cur.fetchall()
             return [id[0] for id in ids]
@@ -177,7 +196,7 @@ class Database(object):
     def add_node(self, node):
         node_tags = list(node)
         node, tags = node_tags[:4], node_tags[-1]
-        self._cur.execute(self._add_node_sql, (node))
+        self._cur.execute(self._add_node_sql, list(map(self._data_wrapper, (node))))  # noqa
         try:
             nid = self._cur.fetchone()[0]
         except TypeError:
@@ -230,8 +249,9 @@ class Database(object):
         """save the random seed and the digested key"""
         self._cur.execute("DELETE  FROM CRYPTO")
         self._cur.execute("INSERT INTO CRYPTO VALUES({}, {})".format(self._sub,
-                                                                     self._sub),
-                          (seed, digest))
+                                                                     self._sub),  # noqa
+
+                          list(map(self._data_wrapper, (seed, digest))))
         self._con.commit()
 
     def loadkey(self):
@@ -242,18 +262,18 @@ class Database(object):
         try:
             self._cur.execute(sql)
             seed, digest = self._cur.fetchone()
-            return seed + u'$6$' + digest
+            return seed + '$6$' + digest
         except TypeError:  # pragma: no cover
             return None
 
     def savekey(self, key):
         salt, digest = key.split('$6$')
-        sql = "INSERT INTO CRYPTO(SEED, DIGEST) VALUES({},{})".format(self._sub,
-                                                                      self._sub)
+        sql = "INSERT INTO CRYPTO(SEED, DIGEST) VALUES({},{})".format(self._sub,  # noqa
+                                                                      self._sub)  # noqa
         self._cur.execute("DELETE FROM CRYPTO")
-        self._cur.execute(sql, (salt, digest))
-        self._digest = digest.encode('utf-8')
-        self._salt = salt.encode('utf-8')
+        self._cur.execute(sql, list(map(self._data_wrapper, (salt, digest))))
+        self._digest = digest.encode()
+        self._salt = salt.encode()
         self._con.commit()
 
     def close(self):  # pragma: no cover

+ 13 - 6
pwman/data/drivers/mongodb.py

@@ -18,6 +18,8 @@
 # ============================================================================
 
 from pwman.data.database import Database, __DB_FORMAT__
+from pwman.util.crypto_engine import CryptoEngine
+
 import pymongo
 
 
@@ -65,14 +67,19 @@ class MongoDB(Database):
 
         return nodes
 
-    def listnodes(self, filter=None):
-        if not filter:
+    def listnodes(self, filter_=None):
+        if not filter_:
             nodes = self._db.nodes.find({}, {'_id': 1})
-
+            return [node["_id"] for node in nodes]
         else:
-            nodes = self._db.nodes.find({"tags": {'$in': [filter]}}, {'_id': 1})
-
-        return [node['_id'] for node in list(nodes)]
+            matching = []
+            ce = CryptoEngine.get()
+            nodes = list(self._db.nodes.find({}, {'_id': 1, 'tags': 1}))
+            for node in nodes:
+                node['tags'] = [ce.decrypt(t) for t in node['tags']]
+                if filter_ in node['tags']:
+                    matching.append(node)
+            return [node["_id"] for node in matching]
 
     def add_node(self, node):
         nid = self._get_next_node_id()

+ 6 - 1
pwman/data/drivers/mysql.py

@@ -24,6 +24,7 @@
 """MySQL Database implementation."""
 from pwman.data.database import Database, __DB_FORMAT__
 
+import pymysql
 import pymysql as mysql
 mysql.install_as_MySQLdb()
 
@@ -61,6 +62,7 @@ class MySQLDatabase(Database):
                               "NOTES) "
                               "VALUES(%s, %s, %s, %s)")
         self._insert_tag_sql = "INSERT INTO TAG(DATA) VALUES(%s)"
+        self._data_wrapper = lambda x: x
         self.ProgrammingError = mysql.ProgrammingError
 
     def _open(self):
@@ -76,4 +78,7 @@ class MySQLDatabase(Database):
                                   passwd=passwd,
                                   db=self.dburi.path.lstrip('/'))
         self._cur = self._con.cursor()
-        self._create_tables()
+        try:
+            self._create_tables()
+        except pymysql.err.InternalError:
+            pass

+ 110 - 0
pwman/data/drivers/postgresql.py

@@ -21,7 +21,9 @@
 
 """Postgresql Database implementation."""
 import psycopg2 as pg
+
 from pwman.data.database import Database, __DB_FORMAT__
+from pwman.util.crypto_engine import CryptoEngine
 
 
 class PostgresqlDatabase(Database):
@@ -68,9 +70,117 @@ class PostgresqlDatabase(Database):
                               'NOTES) VALUES(%s, %s, %s, %s) RETURNING ID')
         self._insert_tag_sql = "INSERT INTO TAG(DATA) VALUES(%s) RETURNING ID"
         self.ProgrammingError = pg.ProgrammingError
+        self._data_wrapper = lambda x: pg.Binary(x)
 
     def _open(self):
 
         self._con = pg.connect(self._pgsqluri.geturl())
         self._cur = self._con.cursor()
         self._create_tables()
+
+    def _get_tag(self, tagcipher):
+        sql_search = "SELECT * FROM TAG"
+        self._cur.execute(sql_search)
+        ce = CryptoEngine.get()
+
+        try:
+            tag = ce.decrypt(tagcipher)
+            encrypted = True
+        except Exception:
+            tag = tagcipher
+            encrypted = False
+
+        rv = self._cur.fetchall()
+        for idx, cipher in rv:
+            cipher = cipher.tobytes()
+            if encrypted and tag == ce.decrypt(cipher):
+                return idx
+            elif tag == cipher:
+                return idx
+
+    def _create_tables(self):
+        if self._check_tables():
+            return
+        try:
+            self._cur.execute("CREATE TABLE NODE(ID SERIAL PRIMARY KEY, "
+                              "USERNAME BYTEA NOT NULL, "
+                              "PASSWORD BYTEA NOT NULL, "
+                              "URL BYTEA NOT NULL, "
+                              "NOTES BYTEA NOT NULL"
+                              ")")
+
+            self._cur.execute("CREATE TABLE TAG"
+                              "(ID  SERIAL PRIMARY KEY,"
+                              "DATA BYTEA NOT NULL)")
+
+            self._cur.execute("CREATE TABLE LOOKUP ("
+                              "nodeid INTEGER NOT NULL REFERENCES NODE(ID),"
+                              "tagid INTEGER NOT NULL REFERENCES TAG(ID)"
+                              ")")
+
+            self._cur.execute("CREATE TABLE CRYPTO "
+                              "(SEED BYTEA, DIGEST BYTEA)")
+
+            self._cur.execute("CREATE TABLE DBVERSION("
+                              "VERSION TEXT NOT NULL)")
+
+            self._cur.execute("INSERT INTO DBVERSION VALUES(%s)",
+                              (self.dbversion,))
+
+            self._con.commit()
+        except Exception:  # pragma: no cover
+            self._con.rollback()
+
+    def savekey(self, key):
+        salt, digest = key.split('$6$')
+        try:
+            salt, digest = salt.encode(), digest.encode()
+        except AttributeError:
+            pass
+
+        sql = "INSERT INTO CRYPTO(SEED, DIGEST) VALUES({},{})".format(self._sub,  # noqa
+                                                                      self._sub)  # noqa
+        self._cur.execute("DELETE FROM CRYPTO")
+        self._cur.execute(sql, list(map(self._data_wrapper, (salt, digest))))
+        self._digest = digest
+        self._salt = salt
+        self._con.commit()
+
+    def loadkey(self):
+        """
+        return _keycrypted
+        """
+        sql = "SELECT * FROM CRYPTO"
+        try:
+            self._cur.execute(sql)
+            seed, digest = self._cur.fetchone()
+            return seed.tobytes() + b'$6$' + digest.tobytes()
+        except TypeError:  # pragma: no cover
+            return None
+
+    def getnodes(self, ids):
+        if ids:
+            sql = ("SELECT * FROM NODE WHERE ID IN ({})"
+                   "".format(','.join(self._sub for i in ids)))
+        else:
+            sql = "SELECT * FROM NODE"
+        self._cur.execute(sql, (ids))
+        nodes = self._cur.fetchall()
+        if not nodes:
+            return []
+
+        nodes_w_tags = []
+        for node in nodes:
+            tags = [t.tobytes() for t in self._get_node_tags(node)]
+            nodes_w_tags.append([node[0]] + [item.tobytes() for item in node[1:]] + tags)
+
+        return nodes_w_tags
+
+    def listtags(self):
+        self._clean_orphans()
+        get_tags = "select DATA from TAG"
+        self._cur.execute(get_tags)
+        tags = self._cur.fetchall()
+        if tags:
+            return [t[0].tobytes() for t in tags]
+        return []  # pragma: no cover

+ 2 - 1
pwman/data/drivers/sqlite.py

@@ -54,6 +54,7 @@ class SQLite(Database):
         self._list_nodes_sql = "SELECT NODEID FROM LOOKUP WHERE TAGID = ? "
         self._insert_tag_sql = "INSERT INTO TAG(DATA) VALUES(?)"
         self._sub = '?'
+        self._data_wrapper = lambda x: x
 
     def _open(self):
         self._con = sqlite.connect(self._filename)
@@ -74,7 +75,7 @@ class SQLite(Database):
 
         self._cur.execute("CREATE TABLE TAG"
                           "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
-                          "DATA BLOB NOT NULL UNIQUE)")
+                          "DATA BLOB NOT NULL)")
 
         self._cur.execute("CREATE TABLE LOOKUP ("
                           "nodeid INTEGER NOT NULL, "

+ 2 - 2
pwman/data/nodes.py

@@ -78,7 +78,7 @@ class Node(object):
             node._password = password.strip()
             node._url = url.strip()
             node._notes = notes.strip()
-            node._tags = [t.strip() for t in tags] 
+            node._tags = [t.strip() for t in tags]
         else:
             node._username = bytes(username, 'utf8').strip()
             node._password = bytes(password, 'utf8').strip()
@@ -123,7 +123,7 @@ class Node(object):
     def tags(self):
         enc = CryptoEngine.get()
         try:
-            return [enc.decrypt(tag).decode() for tag in
+            return [enc.decrypt(tag) for tag in
                     filter(None, self._tags)]
         except Exception:
             return [tag for tag in filter(None, self._tags)]

+ 1 - 1
pwman/exchange/importer.py

@@ -52,7 +52,7 @@ class CSVImporter(BaseImporter):
         try:
             fh, delim = open(self.args.file_delim[0]), self.args.file_delim[1]
             csv_f = csv.reader(fh, delimiter=delim)
-        except IOError:
+        except FileNotFoundError:
             fh, delim = open(self.args.file_delim[1]), self.args.file_delim[0]
             csv_f = csv.reader(fh, delimiter=delim)
 

+ 129 - 126
pwman/ui/baseui.py

@@ -64,38 +64,38 @@ class HelpUIMixin(object):  # pragma: no cover
     """
 
     def _usage(self, string):
-        print ("Usage: %s" % (string))
+        print("Usage: %s" % (string))
 
     def help_open(self):
         self._usage("open|o <ID>")
-        print ("Launch default browser with 'xdg-open url',\n",
-               "the url must contain http:// or https://.")
+        print("Launch default browser with 'xdg-open url',\n",
+              "the url must contain http:// or https://.")
 
     def help_copy(self):
         self._usage("copy|cp <ID>")
-        print ("Copy password to X clipboard (xsel required)")
+        print("Copy password to X clipboard (xsel required)")
 
     def help_cls(self):
         self._usage("cls")
-        print ("Clear the Screen from information.")
+        print("Clear the Screen from information.")
 
     def help_list(self):
         self._usage("list|ls <tag> ...")
-        print ("List nodes that match current or specified filter.",
-               " ls is an alias.")
+        print("List nodes that match current or specified filter.",
+              " ls is an alias.")
 
     def help_delete(self):
         self._usage("delete|rm <ID|tag> ...")
-        print ("Deletes nodes.")
+        print("Deletes nodes.")
         self._mult_id_help()
 
     def help_help(self):
         self._usage("help|h [topic]")
-        print ("Prints a help message for a command.")
+        print("Prints a help message for a command.")
 
     def help_edit(self):
         self._usage("edit <ID|tag> ... ")
-        print ("Edits a nodes.")
+        print("Edits a nodes.")
 
     def help_export(self):
         self._usage("export [{'filename': 'foo.csv', 'delimiter':'|'}] ")
@@ -103,13 +103,13 @@ class HelpUIMixin(object):  # pragma: no cover
 
     def help_new(self):
         self._usage("new")
-        print ("Creates a new node.,",
-               "You can override default config settings the following way:\n",
-               "pwman> n {'leetify':False, 'numerics':True}")
+        print("Creates a new node.,",
+              "You can override default config settings the following way:\n",
+              "pwman> n {'leetify':False, 'numerics':True}")
 
     def help_print(self):
         self._usage("print <ID|tag> ...")
-        print ("Displays a node. ")
+        print("Displays a node. ")
         self._mult_id_help()
 
     def _mult_id_help(self):
@@ -175,19 +175,15 @@ class AliasesMixin(object):  # pragma: no cover
         self.do_new(arg)
 
 
-class BaseCommands(HelpUIMixin, AliasesMixin):
+class BaseUtilsMixin:
 
-    @property
-    def _xsel(self):  # pragma: no cover
-        if self.hasxsel:
-            return True
-
-    def do_EOF(self, args):
-        return self.do_exit(args)
+    """Helper class for storing all privates, useful for testing these methods
+    directly
+    """
 
     def _get_ids(self, args):
         """
-        Command can get a single ID or
+        Each command can get a single ID or
         a range of IDs, with begin-end.
         e.g. 1-3 , will get 1 to 3.
         """
@@ -209,6 +205,105 @@ class BaseCommands(HelpUIMixin, AliasesMixin):
             print("Could not understand your input...")
         return ids
 
+    def _get_tags(self, default=None, reader=raw_input):
+        """
+        Read tags from user input.
+        Tags are simply returned as a list
+        """
+        # TODO: add method to read tags from db, so they
+        # could be used for tab completer
+        print("Tags: ", end="")
+        sys.stdout.flush()
+        taglist = sys.stdin.readline()
+        tagstrings = taglist.split()
+        tags = [tn for tn in tagstrings]
+        return tags
+
+    def _prep_term(self):
+        self.do_cls('')
+        if sys.platform != 'win32':
+            rows, cols = tools.gettermsize()
+        else:  # pragma: no cover
+            rows, cols = 18, 80  # fix this !
+
+        return rows, cols
+
+    def _format_line(self, tag_pad, nid="ID", user="USER", url="URL",
+                     tags="TAGS"):
+        return ("{ID:<3} {USER:<{us}}{URL:<{ur}}{Tags:<{tg}}"
+                "".format(ID=nid, USER=user,
+                          URL=url, Tags=tags, us=25,
+                          ur=25, tg=20))
+
+    def _print_node_line(self, node, rows, cols):
+        tagstring = ','.join([t.decode() for t in node.tags])
+        fmt = self._format_line(cols, node._id, node.username,
+                                node.url[:20] + '...' if (len(node.url) > 22)
+                                else node.url,
+                                tagstring)
+        formatted_entry = tools.typeset(fmt, Fore.YELLOW, False)
+        print(formatted_entry)
+
+    def _get_node_ids(self, args):
+        filter = None
+        if args:
+            filter = args.split()[0]
+            ce = CryptoEngine.get()
+            filter = ce.encrypt(filter)
+        nodeids = self._db.listnodes(filter=filter)
+        return nodeids
+
+    def _db_entries_to_nodes(self, raw_nodes):
+        _nodes_inst = []
+        # user, pass, url, notes
+        for node in raw_nodes:
+            _nodes_inst.append(Node.from_encrypted_entries(
+                node[1],
+                node[2],
+                node[3],
+                node[4],
+                node[5:]))
+            _nodes_inst[-1]._id = node[0]
+        return _nodes_inst
+
+    def _get_input(self, prompt):
+        print(prompt, end="")
+        sys.stdout.flush()
+        return sys.stdin.readline().strip()
+
+    def _get_secret(self):
+        if sys.stdin.isatty():  # pragma: no cover
+            p = get_or_create_pass()
+        else:
+            p = sys.stdin.readline().rstrip()
+        return p
+
+    def _do_new(self, args):
+        node = {}
+        node['username'] = self._get_input("Username: ")
+        node['password'] = self._get_secret()
+        node['url'] = self._get_input("Url: ")
+        node['notes'] = self._get_input("Notes: ")
+        node['tags'] = self._get_tags()
+        node = Node(clear_text=True, **node)
+        self._db.add_node(node)
+        return node
+
+    def _do_rm(self, nodes):
+        for i in nodes:
+            self._db.removenodes([i])
+
+
+class BaseCommands(HelpUIMixin, AliasesMixin, BaseUtilsMixin):
+
+    @property
+    def _xsel(self):  # pragma: no cover
+        if self.hasxsel:
+            return True
+
+    def do_EOF(self, args):
+        return self.do_exit(args)
+
     def error(self, exception):  # pragma: no cover
         if (isinstance(exception, KeyboardInterrupt)):
             print('')
@@ -288,7 +383,7 @@ class BaseCommands(HelpUIMixin, AliasesMixin):
                                                 node[4],
                                                 node[5:])
                 tags = n.tags
-                tags = ','.join(t.strip() for t in tags)
+                tags = ','.join(t.strip().decode() for t in tags)
                 r = list([n.username, n.url, n.password, n.notes])
                 writer.writerow(r + [tags])
 
@@ -317,67 +412,6 @@ class BaseCommands(HelpUIMixin, AliasesMixin):
         for t in tags:
             print(ce.decrypt(t).decode())
 
-    def _get_tags(self, default=None, reader=raw_input):
-        """
-        Read tags from user input.
-        Tags are simply returned as a list
-        """
-        # TODO: add method to read tags from db, so they
-        # could be used for tab completer
-        print("Tags: ", end="")
-        sys.stdout.flush()
-        taglist = sys.stdin.readline()
-        tagstrings = taglist.split()
-        tags = [tn for tn in tagstrings]
-        return tags
-
-    def _prep_term(self):
-        self.do_cls('')
-        if sys.platform != 'win32':
-            rows, cols = tools.gettermsize()
-        else:  # pragma: no cover
-            rows, cols = 18, 80  # fix this !
-
-        return rows, cols
-
-    def _format_line(self, tag_pad, nid="ID", user="USER", url="URL",
-                     tags="TAGS"):
-        return ("{ID:<3} {USER:<{us}}{URL:<{ur}}{Tags:<{tg}}"
-                "".format(ID=nid, USER=user,
-                          URL=url, Tags=tags, us=25,
-                          ur=25, tg=20))
-
-    def _print_node_line(self, node, rows, cols):
-        tagstring = ','.join([t for t in node.tags])
-        fmt = self._format_line(cols, node._id, node.username,
-                                node.url[:20] + '...' if (len(node.url) > 22)
-                                else node.url,
-                                tagstring)
-        formatted_entry = tools.typeset(fmt, Fore.YELLOW, False)
-        print(formatted_entry)
-
-    def _get_node_ids(self, args):
-        filter = None
-        if args:
-            filter = args.split()[0]
-            ce = CryptoEngine.get()
-            filter = ce.encrypt(filter)
-        nodeids = self._db.listnodes(filter=filter)
-        return nodeids
-
-    def _db_entries_to_nodes(self, raw_nodes):
-        _nodes_inst = []
-        # user, pass, url, notes
-        for node in raw_nodes:
-            _nodes_inst.append(Node.from_encrypted_entries(
-                node[1],
-                node[2],
-                node[3],
-                node[4],
-                node[5:]))
-            _nodes_inst[-1]._id = node[0]
-        return _nodes_inst
-
     def do_edit(self, args, menu=None):
         ids = self._get_ids(args)
         for i in ids:
@@ -391,13 +425,13 @@ class BaseCommands(HelpUIMixin, AliasesMixin):
             node = Node.from_encrypted_entries(*node)
             if not menu:
                 menu = CMDLoop(self.config)
-                print ("Editing node %d." % (i))
+                print("Editing node %d." % (i))
                 menu.add(CliMenuItem("Username", node.username))
                 menu.add(CliMenuItem("Password",  node.password))
                 menu.add(CliMenuItem("Url", node.url))
                 menunotes = CliMenuItem("Notes", node.notes)
                 menu.add(menunotes)
-                menu.add(CliMenuItem("Tags", ','.join(node.tags)))
+                menu.add(CliMenuItem("Tags", ','.join(map(lambda x: x.decode(), node.tags))))  # noqa
             menu.run(node)
             self._db.editnode(i, **node.to_encdict())
             # when done with node erase it
@@ -416,31 +450,8 @@ class BaseCommands(HelpUIMixin, AliasesMixin):
         for idx, node in enumerate(_nodes_inst):
             self._print_node_line(node, rows, cols)
 
-    def _get_input(self, prompt):
-        print(prompt, end="")
-        sys.stdout.flush()
-        return sys.stdin.readline().strip()
-
-    def _get_secret(self):
-        if sys.stdin.isatty():  # pragma: no cover
-            p = get_or_create_pass()
-        else:
-            p = sys.stdin.readline().rstrip()
-        return p
-
-    def _do_new(self, args):
-        node = {}
-        node['username'] = self._get_input("Username: ")
-        node['password'] = self._get_secret()
-        node['url'] = self._get_input("Url: ")
-        node['notes'] = self._get_input("Notes: ")
-        node['tags'] = self._get_tags()
-        node = Node(clear_text=True, **node)
-        self._db.add_node(node)
-        return node
-
     def do_new(self, args):  # pragma: no cover
-        # The cmd module stops if and of do_* return something
+        # The cmd module stops if any of do_* return something
         # else than None ...
         # This is bad for testing, so everything that is do_*
         # should call _do_* method which is testable
@@ -465,21 +476,13 @@ class BaseCommands(HelpUIMixin, AliasesMixin):
         _wait_until_enter(_heard_enter, float(flushtimeout))
         self.do_cls('')
 
-    def _do_rm(self, args):
-        for i in args.split():
-            if not i.isdigit():
-                print("%s is not a node ID" % i)
-                return None
-
-        for i in args.split():
-            ans = tools.getinput(("Are you sure you want to delete node {}"
-                                  " [y/N]?".format(i)))
-            if ans.lower() == 'y':
-                self._db.removenodes([i])
-
-    def do_delete(self, args):  # pragma: no cover
-        CryptoEngine.get()
-        self._do_rm(args)
+    def do_delete(self, args):
+        ids = self._get_ids(args)
+        ans = tools.getinput("Are you sure you want to delete node{} {}"
+                             " [y/N]?".format("s" if len(ids) > 1 else "",
+                                              ",".join(ids) if len(ids) > 1 else ids[0]))  # noqa
+        if ans.lower() == 'y':
+            self._do_rm(args)
 
     def do_info(self, args):
         print("Currently connected to: {}".format(

+ 20 - 9
pwman/ui/cli.py

@@ -14,14 +14,12 @@
 # along with Pwman3; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 # ============================================================================
-# Copyright (C) 2012, 2013, 2014, 2015 Oz Nahum <nahumoz@gmail.com>
+# Copyright (C) 2012-2016 Oz N Tiram <oz.tiram@gmail.com>
 # ============================================================================
 # pylint: disable=I0011
 
-from __future__ import print_function
 
 import cmd
-import os
 import sys
 
 
@@ -35,14 +33,15 @@ except ImportError as e:  # pragma: no cover
     import pyreadline as readline
     _readline_available = True
 
-
 from pwman.ui.baseui import BaseCommands
-from pwman import (get_conf_options, get_db_version, version, website, parser_options)
+from pwman import (get_conf_options, get_db_version, version, website,
+                   parser_options, has_cryptography, calculate_client_info,
+                   is_latest_version)
 from pwman.ui.tools import CLICallback
 from pwman.data import factory
 from pwman.exchange.importer import Importer
 from pwman.util.crypto_engine import CryptoEngine
-from pwman.util.crypto_engine import AES
+
 
 class PwmanCli(cmd.Cmd, BaseCommands):
     """
@@ -100,12 +99,24 @@ def main():
     xselpath, dbtype, config = get_conf_options(args, OSX)
     dburi = config.get_value('Database', 'dburi')
 
-    if os.path.join("Crypto", "Cipher") not in AES.__file__:  # we are using built in AES.py
+    if config.get_value('Updater',
+                        'supress_version_check').lower() != 'yes':
+        client_info = config.get_value('Updater', 'client_info')
+        if not client_info:
+            client_info = calculate_client_info()
+            config.set_value('Updater', 'client_info', client_info)
+
+        _, latest = is_latest_version(version, client_info)
+
+        if not latest:
+            print("A newer version of Pwman3 was release, you should consider updating")  # noqa
+
+    if not has_cryptography:
         import colorama
         if config.get_value('Crypto', 'supress_warning').lower() != 'yes':
             print("{}WARNING: You are not using PyCrypto!!!\n"
-                  "WARNING: You should install PyCrypto for better security and "
-                  "perfomance\nWARNING: You can supress this warning by editing "
+                  "WARNING: You should install PyCrypto for better security and "  # noqa
+                  "perfomance\nWARNING: You can supress this warning by editing "  # noqa
                   "pwman config file.{}".format(colorama.Fore.RED,
                                                 colorama.Style.RESET_ALL))
 

+ 117 - 3
pwman/ui/templates/index.tpl

@@ -5,10 +5,124 @@
         <title>Pwman3 Web</title>
         <meta name="description" content="">
         <meta name="viewport" content="width=device-width, initial-scale=1">
+        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
+        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
         <link rel="stylesheet" href="/static/css/milligram.css">
+	<link rel="stylesheet" href="/static/css/style.css">
     </head>
-<body>
 
-  <h1>Hello Miligram</h1>
-</body>
+
+<main class="wrapper">
+  <nav class="navigation" >
+  <section class="container container-nav">
+    <a class="navigation-title" href="#openModal">
+    <img class="img" src="https://openclipart.org/download/190821/Cles-de-serrure-lock-keys.svg" height="30" alt="pwman3" title="pwman3">
+    <h1 class="title">Pwman3</h1>
+    </a>
+    <span class="float-right" style="display: inline-block; float: right; padding-top: 0.5rem;">
+    <div style="padding-right: 0rem; padding-left: 60px; float: right">
+    <form id="show" class="">
+      <input id="" class="" type="password" name="auth-pwd" placeholder="Password" style="width:60%" >
+      <input id="" class=""  value="Submit" type="submit" style="float: right; width:33.%; display: inline-block">
+    </form>
+    <div>
+    </span>
+
+    <div id="openModal" class="modalDialog">
+      <div>
+        <a href="#close" title="Close" class="close">x</a>
+        <h2>Pwmam3 - Web</h2>
+        <p> Pwman3 is distributed under the GPL v. 3.
+        <p> Pwman3 Logo is made by <pre> enolynn</pre> and was downloaded
+        from openclipart: 
+	<a href="https://openclipart.org/detail/190821/cles-de-serrure-lock-keys">logo</a>. 
+	This logo is distributed under the Creative Commons license.
+        You can learn more about this Software in 
+	<a href="http://pwman3.github.io/pwman3/">pwman3's home page</a>.</div>. 
+    </div>
+    </section>
+  </nav>
+  
+  <section class="container password-box" style="margin-top: 7rem; ">
+  Login: <strong>oz.tiram@gmail.com</strong>
+  <br>Website:<strong><a href="stackoverflow.com">http://stackoverflow.com</a></strong>
+  <br>Password: <strong>********</strong><br>  
+  Notes: Some notes about this entry 
+  <br>
+  
+   <div class="tags float-right">
+    Tags: 
+     <button class="button button-clear button-clear button-tag">net</button>
+     <button class="button button-clear button-tag">learning</button>  
+    </div>
+  </section>
+  <section class="container password-box">
+  <div>
+  Login: <strong>oz.tiram@gmail.com</strong>
+  <br>Website:<strong><a href="stackoverflow.com">http://stackoverflow.com</a></strong>
+  <br>Password: <strong>********</strong><br>  
+  Notes: Some notes about this entry 
+  <br>
+   <div class="tags float-right">
+     <button class="button button-clear button-clear button-tag">net</button>
+     <button class="button button-clear button-tag">learning</button>  
+    </div>
+  </div>  
+  </section>
+  <section class="container password-box">
+  Login: <strong>oz.tiram@gmail.com</strong>
+  <br>Website: <strong><a href="stackoverflow.com">http://stackoverflow.com</a></strong>
+  <br>Password: <strong>********</strong><br>  
+  Notes: Some notes about this entry 
+  <br>
+   <div class="tags float-right">
+     <button class="button button-clear button-clear button-tag">net</button>
+     <button class="button button-clear button-tag">learning</button>  
+    </div>
+  </section>
+  <section class="container password-box">
+  Login: <strong>oz.tiram@gmail.com</strong>
+  <br>Website: <strong><a href="stackoverflow.com">http://stackoverflow.com</a></strong>
+  <br>Password: <strong>********</strong><br>  
+  Notes: Some notes about this entry 
+  <br>
+   <div class="tags float-right">
+     <button class="button button-clear button-clear button-tag">net</button>
+     <button class="button button-clear button-tag">learning</button>  
+    </div>
+  </section>
+  <section class="container password-box">
+  Login: <strong>oz.tiram@gmail.com</strong>
+  <br>Website: <strong><a href="stackoverflow.com">http://stackoverflow.com</a></strong>
+  <br>Password: <strong>********</strong><br>  
+  Notes: Some notes about this entry 
+  <br>
+   <div class="tags float-right">
+     <button class="button button-clear button-clear button-tag">net</button>
+     <button class="button button-clear button-tag">learning</button>  
+    </div>
+  </section>
+  <section class="container password-box">
+  Login: <strong>oz.tiram@gmail.com</strong>
+  <br>Website: <strong><a href="stackoverflow.com">http://stackoverflow.com</a></strong>
+  <br>Password: <strong>********</strong><br>  
+  Notes: Some notes about this entry 
+  <br>
+   <div class="tags float-right">
+     <button class="button button-clear button-clear button-tag">net</button>
+     <button class="button button-clear button-tag">learning</button>  
+    </div>
+  </section>
+
+</main>
+
+<script type="text/javascript">
+$("#auth-btn").click(function () {
+   $("#show").show('fast');
+});
+
+$("#auth-btn").click(function () {
+   $("#auth-btn").hide(1000);
+});
+</script>
 </html>

+ 437 - 0
pwman/ui/templates/static/css/style.css

@@ -0,0 +1,437 @@
+/* Base */
+img {
+	max-width: 100%;
+}
+.wrapper {
+	position: relative;
+	display: block;
+	width: 100%;
+	overflow: hidden;
+}
+
+
+/* Sections */
+
+.container {
+	/*border-top: .1rem solid #d1d1d1;*/
+	padding-top: 7.5rem;
+	padding-bottom: 7.5rem;
+	margin-bottom: 0;
+	max-width: 80.0rem;
+}
+.example .row, .example.row, .example form {
+	margin-bottom: 0;
+}
+.example h1, .example h2, .example h3, .example h4, .example h5, .example h6 {
+	margin-bottom: 1rem;
+}
+
+
+/* Footer */
+
+.footer .container {
+	padding-bottom: 0;
+	padding-top: 0;
+}
+
+
+/* Header */
+
+.header {
+	background-color: #f4f5f6;
+	padding-top: 1rem;
+}
+@media (min-width: 40.0rem) {
+	.header {
+		padding-top: 5rem;
+	}
+}
+.header .container {
+	position: relative;
+	border-top: 0;
+	text-align: center;
+}
+.header .title {
+	font-family: 'Gotham Rounded A', 'Gotham Rounded B', 'Helvetica Neue', Arial, sans-serif;
+}
+.header .img {
+	height: 15.0rem;
+	margin-bottom: 2rem;
+}
+.header + section {
+	border-top: 0;
+}
+.header .button {
+	margin-top: 2rem;
+	margin-bottom: 4rem;
+}
+@media (min-width: 40.0rem) {
+	.header .button {
+		margin-top: 2rem;
+		margin-bottom: 4rem;
+	}
+}
+
+
+
+/* Typography
+*/
+
+.heading-font-size {
+	font-size: 1.2rem;
+	color: #999;
+	letter-spacing: normal;
+}
+
+
+/* Form */
+
+.example-send-yourself-copy {
+	float: right;
+	margin-top: 1.2rem;
+}
+
+
+/* Tips */
+.button-black {
+	background-color: black;
+	border-color: black;
+}
+.button-black.button-outline {
+	color: black;
+}
+.button-black.button-clear {
+	color: black;
+}
+
+.button-large {
+	font-size: 1.4rem;
+	height: 4.5rem;
+	line-height: 4.5rem;
+	padding: 0 2rem;
+}
+
+.button-small {
+	font-size: .8rem;
+	height: 2.8rem;
+	line-height: 2.8rem;
+	padding: 0 1.5rem;
+}
+
+
+/* Snippets, Exemples */
+.example {
+	position: relative;
+	margin-top: 4rem;
+}
+.example-header {
+	font-weight: 600;
+	margin-top: 1.5rem;
+	margin-bottom: .5rem;
+}
+.example-description {
+	margin-bottom: 1.5rem;
+}
+.example-screenshot-wrapper {
+	display: block;
+	position: relative;
+	overflow: hidden;
+	border-radius: .6rem;
+	border: .1rem solid #d1d1d1;
+	height: 25.0rem;
+}
+.example-screenshot {
+	width: 100%;
+	height: auto;
+}
+.example-screenshot.coming-soon {
+	width: auto;
+	position: absolute;
+	background: #d1d1d1;
+	top: .5rem;
+	right: .5rem;
+	bottom: .5rem;
+	left: .5rem;
+}
+.example-screenshot-wrapper {
+	position: absolute;
+	width: 48%;
+	height: 100%;
+	left: 0;
+	max-height: none;
+}
+
+
+/* Navbar */
+.navigation {
+	left: 0;
+	max-width: 100%;
+	position: fixed;
+	right: 0;
+	top: 0;
+	max-width: 100vw;
+	z-index: 99;
+}
+/* Re-overiding the width 100% declaration to match size of% based container */
+.navigation .container {
+	padding-top: 0;
+	padding-bottom: 0;
+}
+.navigation {
+	background: #f4f5f6;
+	border-bottom: .1rem solid #d1d1d1;
+	display: inline-block;
+	height: 5.2rem;
+	width: 100%;
+}
+
+.auth {
+  display: inline-block;
+  float: right;
+}
+
+.auth .button {
+  display: inline-block;
+  float: right;
+  vertical-align: middle;
+  margin-top: 0.8rem;
+}
+
+.auth .navigation-button {
+  display: inline-block;
+  width: 25%;
+  vertical-align: middle;
+  float: left;
+  margin-left: 10px;
+}
+
+
+.auth input[type="password"] {
+   width: 55%;
+   display: inline-block;
+   margin-left: 10px;
+   float: left;
+   text-align: left;
+   vertical-align: middle;
+}
+
+@media (min-width: 80.0rem) {
+	.navigation-list {
+		margin-right: 0;
+	}
+}
+.navigation-item {
+	float: left;
+	margin-bottom: 0;
+	margin-left: 2.5rem;
+	position: relative;
+}
+.navigation .img {
+	position: relative;
+	top: .3rem;
+	height: 2.0rem;
+}
+
+.navigation .title,
+.navigation-title {
+	color: #606c76;
+	display: inline;
+	font-family: 'Gotham Rounded A', 'Gotham Rounded B', 'Helvetica Neue', Arial, sans-serif;
+	line-height: 5.2rem;
+	font-size: 1.6rem;
+	padding: 0;
+	position: relative;
+	text-decoration: none;
+}
+
+.navigation-button {
+	font-familiy: "Roboto";
+  display: inline-block;
+	position: relative;
+	font-size: 1.6rem;
+  padding: 0px 1.3rem;
+  margin-top: 1rem;
+  float: right;
+
+}
+.navigation-link {
+	display: inline;
+	line-height: 5.2rem;
+	font-size: 1.6rem;
+	padding: 0;
+	text-decoration: none;
+}
+.navigation-link.active {
+	color: #606c76;
+}
+
+@-webkit-keyframes octocat-wave {
+	0% , 50% {
+		-webkit-transform: rotate(0);
+		-moz-transform: rotate(0);
+		-ms-transform: rotate(0);
+		-o-transform: rotate(0);
+		transform: rotate(0);
+	}
+	25% , 75% {
+		-webkit-transform: rotate(-25deg);
+		-moz-transform: rotate(-25deg);
+		-ms-transform: rotate(-25deg);
+		-o-transform: rotate(-25deg);
+		transform: rotate(-25deg);
+	}
+}
+@-moz-keyframes octocat-wave {
+	0% , 50% {
+		-webkit-transform: rotate(0);
+		-moz-transform: rotate(0);
+		-ms-transform: rotate(0);
+		-o-transform: rotate(0);
+		transform: rotate(0);
+	}
+	25% , 75% {
+		-webkit-transform: rotate(-25deg);
+		-moz-transform: rotate(-25deg);
+		-ms-transform: rotate(-25deg);
+		-o-transform: rotate(-25deg);
+		transform: rotate(-25deg);
+	}
+}
+@-ms-keyframes octocat-wave {
+	0% , 50% {
+		-webkit-transform: rotate(0);
+		-moz-transform: rotate(0);
+		-ms-transform: rotate(0);
+		-o-transform: rotate(0);
+		transform: rotate(0);
+	}
+	25% , 75% {
+		-webkit-transform: rotate(-25deg);
+		-moz-transform: rotate(-25deg);
+		-ms-transform: rotate(-25deg);
+		-o-transform: rotate(-25deg);
+		transform: rotate(-25deg);
+	}
+}
+@-o-keyframes octocat-wave {
+	0% , 50% {
+		-webkit-transform: rotate(0);
+		-moz-transform: rotate(0);
+		-ms-transform: rotate(0);
+		-o-transform: rotate(0);
+		transform: rotate(0);
+	}
+	25% , 75% {
+		-webkit-transform: rotate(-25deg);
+		-moz-transform: rotate(-25deg);
+		-ms-transform: rotate(-25deg);
+		-o-transform: rotate(-25deg);
+		transform: rotate(-25deg);
+	}
+}
+@keyframes octocat-wave {
+	0% , 50% {
+		-webkit-transform: rotate(0);
+		transform: rotate(0);
+	}
+	25% , 75% {
+		-webkit-transform: rotate(-25deg);
+		transform: rotate(-25deg);
+	}
+}
+
+
+@media (min-width: 40.0rem) {
+	.only-mobile {
+		display: none;
+	}
+}
+
+
+.password-box {
+  border: 0.1rem solid #D1D1D1;
+  border-radius: 0.4rem;
+  margin-bottom: 1rem;
+	padding-top: 2rem;
+  padding-bottom: 4rem;
+}
+
+.container-nav {
+  padding-right: 0px;
+}
+
+
+.tags {
+
+  height: 10%;
+  position: relative;
+	margin-top: 0;
+	padding-top: 0rem;
+	top: 80%;
+}
+
+.password-box strong {
+    font-family: 'ColaborateMediumRegular', Arial, sans-serif;
+    font-weight: 700;
+}
+.tag-button {
+  
+}
+
+.button-tag {
+  padding: 0px 0px;
+}
+
+.modalDialog {
+		position: fixed;
+		font-family: Arial, Helvetica, sans-serif;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		left: 0;
+		background: rgba(0,0,0,0.8);
+		z-index: 99999;
+		opacity:0;
+		-webkit-transition: opacity 400ms ease-in;
+		-moz-transition: opacity 400ms ease-in;
+		transition: opacity 400ms ease-in;
+		pointer-events: none;
+	}
+
+	.modalDialog:target {
+		opacity:1;
+		pointer-events: auto;
+	}
+
+	.modalDialog > div {
+		width: 400px;
+		position: relative;
+		margin: 10% auto;
+		padding: 5px 20px 13px 20px;
+		border-radius: 10px;
+		background: #fff;
+		background: -moz-linear-gradient(#fff, #999);
+		background: -webkit-linear-gradient(#fff, #999);
+		background: -o-linear-gradient(#fff, #999);
+	}
+
+	.close {
+		background: #606061;
+		color: #FFFFFF;
+		line-height: 25px;
+		position: absolute;
+		right: -12px;
+		text-align: center;
+		top: -10px;
+		width: 24px;
+		text-decoration: none;
+		font-weight: bold;
+		-webkit-border-radius: 12px;
+		-moz-border-radius: 12px;
+		border-radius: 12px;
+		-moz-box-shadow: 1px 1px 3px #000;
+		-webkit-box-shadow: 1px 1px 3px #000;
+		box-shadow: 1px 1px 3px #000;
+	}
+
+	.close:hover { background: #00d9ff; }

+ 6 - 3
pwman/util/config.py

@@ -32,7 +32,7 @@ config_dir = os.path.expanduser("~/.pwman")
 
 default_config = {'Global': {'umask': '0100', 'colors': 'yes',
                              'cls_timeout': '10', 'cp_timeout': '5',
-                             'save': 'True'
+                             'save': 'True', 'supress_version_check': 'no'
                              },
                   'Database': {
                       'dburi': 'sqlite://' + os.path.join(config_dir,
@@ -40,10 +40,13 @@ default_config = {'Global': {'umask': '0100', 'colors': 'yes',
                   'Readline': {'history': os.path.join(config_dir,
                                                        'history')},
                   'Crypto': {'supress_warning': 'no'},
+
+                  'Updater': {'supress_version_check': 'no'}
                   }
 
 if 'win' in sys.platform:
-    default_config['Database']['dburi'] = default_config['Database']['dburi'].replace("\\", "/")
+    default_config['Database']['dburi'] = default_config['Database']['dburi'].replace("\\", "/")  # noqa
+
 
 class ConfigException(Exception):
     """Basic exception for config."""
@@ -108,7 +111,7 @@ class Config(object):
         self.parser.set(section, name, value)
 
     def save(self):
-        if not "False" in self.get_value("Global", "Save"):
+        if "False" not in self.get_value("Global", "Save"):
             with open(self.filename, "w") as fp:
                 self.parser.write(fp)
 

+ 0 - 332
pwman/util/crypto/AES.py

@@ -1,332 +0,0 @@
-"""
-
-Copyright (c) 2014  Philippe Teuwen <phil@teuwen.org>
-
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of 
-this software and associated documentation files (the "Software"), to deal in 
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
-IN THE SOFTWARE.
-
-This code is taken from https://github.com/doegox/python-cryptoplus/
-
-"""
-from .blockcipher import *
-from .rijndael import rijndael
-
-def new(key,mode=MODE_ECB,IV=None,counter=None,segment_size=None):
-    """Create a new cipher object
-
-    Wrapper for pure python implementation rijndael.py
-
-        key = raw string containing the key, AES-128..256 will be selected according to the key length
-            -> when using XTS mode: the key should be a tuple containing the 2 keys needed
-        mode = python_AES.MODE_ECB/CBC/CFB/OFB/CTR/XTS/CMAC, default is ECB
-            -> for every mode, except ECB and CTR, it is important to construct a seperate cipher for encryption and decryption
-        IV = IV as a raw string, default is "all zero" IV
-            -> needed for CBC, CFB and OFB mode
-        counter = counter object (CryptoPlus.Util.util.Counter)
-            -> only needed for CTR mode
-            -> use a seperate counter object for the cipher and decipher: the counter is updated directly, not a copy
-                see CTR example further on in the docstring
-        segment_size = amount of bits to use from the keystream in each chain part
-            -> supported values: multiple of 8 between 8 and the blocksize
-               of the cipher (only per byte access possible), default is 8
-            -> only needed for CFB mode
-
-    Notes:
-        - Always construct a seperate cipher object for encryption and decryption. Once a cipher object has been used for encryption,
-          it can't be used for decryption because it keeps a state (if necessary) for the IV.
-
-    EXAMPLES:
-    **********
-    IMPORTING:
-    -----------
-    >>> from CryptoPlus.Cipher import python_AES
-
-    ECB EXAMPLE:
-    -------------
-    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
-
-    >>> cipher = python_AES.new('2b7e151628aed2a6abf7158809cf4f3c'.decode('hex'))
-    >>> crypted = cipher.encrypt('6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex'))
-    >>> crypted.encode('hex')
-    '3ad77bb40d7a3660a89ecaf32466ef97f5d3d58503b9699de785895a96fdbaaf'
-    >>> decipher = python_AES.new('2b7e151628aed2a6abf7158809cf4f3c'.decode('hex'))
-    >>> decipher.decrypt(crypted).encode('hex')
-    '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'
-
-    PADDING EXAMPLE:
-    -----------------
-    >>> cipher = python_AES.new('0123456789012345')
-    >>> crypt = cipher.encrypt('0123456789012')
-    >>> crypt += cipher.final()
-    >>> decipher = python_AES.new('0123456789012345')
-    >>> decipher.decrypt(crypt)
-    '0123456789012\\x03\\x03\\x03'
-
-    CBC EXAMPLE (plaintext = 3 blocksizes):
-    -----------------------------------------
-    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
-
-    >>> key = ('2b7e151628aed2a6abf7158809cf4f3c').decode('hex')
-    >>> IV = ('000102030405060708090a0b0c0d0e0f').decode('hex')
-    >>> plaintext1 = ('6bc1bee22e409f96e93d7e117393172a').decode('hex')
-    >>> plaintext2 = ('ae2d8a571e03ac9c9eb76fac45af8e51').decode('hex')
-    >>> plaintext3 = ('30c81c46a35ce411e5fbc1191a0a52ef').decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_CBC,IV)
-    >>> ciphertext = cipher.encrypt(plaintext1 + plaintext2 + plaintext3)
-    >>> (ciphertext).encode('hex')
-    '7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e22229516'
-    >>> decipher = python_AES.new(key,python_AES.MODE_CBC,IV)
-    >>> plaintext = decipher.decrypt(ciphertext)
-    >>> (plaintext).encode('hex')
-    '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52ef'
-
-    OR: supply plaintext as seperate pieces:
-    ------------------------------------------
-    >>> cipher = python_AES.new(key,python_AES.MODE_CBC,IV)
-    >>> ( cipher.encrypt(plaintext1 + plaintext2[:-2]) ).encode('hex')
-    '7649abac8119b246cee98e9b12e9197d'
-    >>> ( cipher.encrypt(plaintext2[-2:] + plaintext3) ).encode('hex')
-    '5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e22229516'
-    >>> decipher = python_AES.new(key,python_AES.MODE_CBC,IV)
-    >>> (decipher.decrypt(ciphertext[:22])).encode('hex')
-    '6bc1bee22e409f96e93d7e117393172a'
-    >>> (decipher.decrypt(ciphertext[22:])).encode('hex')
-    'ae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52ef'
-
-    CFB EXAMPLE: (CFB8-AES192)
-    ------------
-    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
-    
-    >>> key = '2b7e151628aed2a6abf7158809cf4f3c'.decode('hex')
-    >>> IV = '000102030405060708090a0b0c0d0e0f'.decode('hex')
-    >>> plain = '6bc1bee22e409f96e93d7e117393172a'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_CFB,IV=IV,segment_size=8)
-    >>> ciphertext = cipher.encrypt(plain)
-    >>> ciphertext.encode('hex')
-    '3b79424c9c0dd436bace9e0ed4586a4f'
-    >>> decipher = python_AES.new(key,python_AES.MODE_CFB,IV)
-    >>> decipher.decrypt(ciphertext).encode('hex')
-    '6bc1bee22e409f96e93d7e117393172a'
-
-    CFB EXAMPLE: (CFB128-AES192)
-    ------------
-    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
-
-    >>> key = '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'.decode('hex')
-    >>> IV = '000102030405060708090a0b0c0d0e0f'.decode('hex')
-    >>> plain = '6bc1bee22e409f96e93d7e117393172a'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_CFB,IV=IV,segment_size=128)
-    >>> output1 = cipher.encrypt(plain)
-    >>> output1.encode('hex')
-    'cdc80d6fddf18cab34c25909c99a4174'
-    >>> plain = 'ae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex')
-    >>> output2 = cipher.encrypt(plain)
-    >>> output2.encode('hex')
-    '67ce7f7f81173621961a2b70171d3d7a'
-    >>> decipher = python_AES.new(key,python_AES.MODE_CFB,IV=IV,segment_size=128)
-    >>> decipher.decrypt(output1+output2).encode('hex')
-    '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'
-
-    CFB EXAMPLE: same as previous but now as a streamcipher
-    ------------
-    >>> key = '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'.decode('hex')
-    >>> IV = '000102030405060708090a0b0c0d0e0f'.decode('hex')
-    >>> plain = '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_CFB,IV=IV,segment_size=128)
-    >>> output = ''
-    >>> for i in plain: output += cipher.encrypt(i)
-    >>> output.encode('hex')
-    'cdc80d6fddf18cab34c25909c99a417467ce7f7f81173621961a2b70171d3d7a'
-
-    OFB EXAMPLE: (OFB128-AES192)
-    ------------
-    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
-
-    >>> key = '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'.decode('hex')
-    >>> IV = '000102030405060708090a0b0c0d0e0f'.decode('hex')
-    >>> plain = '6bc1bee22e409f96e93d7e117393172a'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_OFB,IV)
-    >>> output1 = cipher.encrypt(plain)
-    >>> output1.encode('hex')
-    'cdc80d6fddf18cab34c25909c99a4174'
-    >>> plain = 'ae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex')
-    >>> output2 = cipher.encrypt(plain)
-    >>> output2.encode('hex')
-    'fcc28b8d4c63837c09e81700c1100401'
-    >>> decipher = python_AES.new(key,python_AES.MODE_OFB,IV)
-    >>> decipher.decrypt(output1 + output2).encode('hex')
-    '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'
-
-    OFB EXAMPLE: same as previous but now as a streamcipher
-    ------------
-    >>> key = '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'.decode('hex')
-    >>> IV = '000102030405060708090a0b0c0d0e0f'.decode('hex')
-    >>> plain = '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_OFB,IV)
-    >>> output = ''
-    >>> for i in plain: output += cipher.encrypt(i)
-    >>> output.encode('hex')
-    'cdc80d6fddf18cab34c25909c99a4174fcc28b8d4c63837c09e81700c1100401'
-
-
-    CTR EXAMPLE:
-    ------------
-    NIST Special Publication 800-38A http://cryptome.org/bcm/sp800-38a.htm#F
-
-    >>> from CryptoPlus.Util.util import Counter
-    >>> key = '2b7e151628aed2a6abf7158809cf4f3c'.decode('hex')
-    >>> counter = Counter('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'.decode('hex'))
-    >>> cipher = python_AES.new(key,python_AES.MODE_CTR,counter=counter)
-    >>> plaintext1 = '6bc1bee22e409f96e93d7e117393172a'.decode('hex')
-    >>> plaintext2 = 'ae2d8a571e03ac9c9eb76fac45af8e51'.decode('hex')
-    >>> plaintext3 = '30c81c46a35ce411e5fbc1191a0a52ef'.decode('hex')
-    >>> ciphertext = cipher.encrypt(plaintext1 + plaintext2 + plaintext3)
-    >>> ciphertext.encode('hex')
-    '874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab'
-    >>> counter2 = Counter('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'.decode('hex'))
-    >>> decipher = python_AES.new(key,python_AES.MODE_CTR,counter=counter2)
-    >>> decipher.decrypt(ciphertext).encode('hex')
-    '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52ef'
-
-    XTS EXAMPLE:
-    ------------
-    XTS-AES-128 applied for a data unit of 512 bytes
-    IEEE P1619/D16: http://grouper.ieee.org/groups/1619/email/pdf00086.pdf
-
-    >>> key = ('27182818284590452353602874713526'.decode('hex'),'31415926535897932384626433832795'.decode('hex'))
-    >>> plaintext = '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> ciphertext = cipher.encrypt(plaintext)
-    >>> ciphertext.encode('hex')
-    '27a7479befa1d476489f308cd4cfa6e2a96e4bbe3208ff25287dd3819616e89cc78cf7f5e543445f8333d8fa7f56000005279fa5d8b5e4ad40e736ddb4d35412328063fd2aab53e5ea1e0a9f332500a5df9487d07a5c92cc512c8866c7e860ce93fdf166a24912b422976146ae20ce846bb7dc9ba94a767aaef20c0d61ad02655ea92dc4c4e41a8952c651d33174be51a10c421110e6d81588ede82103a252d8a750e8768defffed9122810aaeb99f9172af82b604dc4b8e51bcb08235a6f4341332e4ca60482a4ba1a03b3e65008fc5da76b70bf1690db4eae29c5f1badd03c5ccf2a55d705ddcd86d449511ceb7ec30bf12b1fa35b913f9f747a8afd1b130e94bff94effd01a91735ca1726acd0b197c4e5b03393697e126826fb6bbde8ecc1e08298516e2c9ed03ff3c1b7860f6de76d4cecd94c8119855ef5297ca67e9f3e7ff72b1e99785ca0a7e7720c5b36dc6d72cac9574c8cbbc2f801e23e56fd344b07f22154beba0f08ce8891e643ed995c94d9a69c9f1b5f499027a78572aeebd74d20cc39881c213ee770b1010e4bea718846977ae119f7a023ab58cca0ad752afe656bb3c17256a9f6e9bf19fdd5a38fc82bbe872c5539edb609ef4f79c203ebb140f2e583cb2ad15b4aa5b655016a8449277dbd477ef2c8d6c017db738b18deb4a427d1923ce3ff262735779a418f20a282df920147beabe421ee5319d0568'
-    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> decipher.decrypt(ciphertext).encode('hex')
-    '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
-
-    using data sequence number n
-
-    >>> key = ('fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0'.decode('hex'),'22222222222222222222222222222222'.decode('hex'))
-    >>> plain ='4444444444444444444444444444444444444444444444444444444444444444'.decode('hex')
-    >>> n = '3333333333'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> ciphertext = cipher.encrypt(plain,n)
-    >>> ciphertext.encode('hex')
-    'af85336b597afc1a900b2eb21ec949d292df4c047e0b21532186a5971a227a89'
-    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> decipher.decrypt(ciphertext,n).encode('hex')
-    '4444444444444444444444444444444444444444444444444444444444444444'
-
-    >>> key = ('27182818284590452353602874713526'.decode('hex'),'31415926535897932384626433832795'.decode('hex'))
-    >>> plain ='72efc1ebfe1ee25975a6eb3aa8589dda2b261f1c85bdab442a9e5b2dd1d7c3957a16fc08e526d4b1223f1b1232a11af274c3d70dac57f83e0983c498f1a6f1aecb021c3e70085a1e527f1ce41ee5911a82020161529cd82773762daf5459de94a0a82adae7e1703c808543c29ed6fb32d9e004327c1355180c995a07741493a09c21ba01a387882da4f62534b87bb15d60d197201c0fd3bf30c1500a3ecfecdd66d8721f90bcc4c17ee925c61b0a03727a9c0d5f5ca462fbfa0af1c2513a9d9d4b5345bd27a5f6e653f751693e6b6a2b8ead57d511e00e58c45b7b8d005af79288f5c7c22fd4f1bf7a898b03a5634c6a1ae3f9fae5de4f296a2896b23e7ed43ed14fa5a2803f4d28f0d3ffcf24757677aebdb47bb388378708948a8d4126ed1839e0da29a537a8c198b3c66ab00712dd261674bf45a73d67f76914f830ca014b65596f27e4cf62de66125a5566df9975155628b400fbfb3a29040ed50faffdbb18aece7c5c44693260aab386c0a37b11b114f1c415aebb653be468179428d43a4d8bc3ec38813eca30a13cf1bb18d524f1992d44d8b1a42ea30b22e6c95b199d8d182f8840b09d059585c31ad691fa0619ff038aca2c39a943421157361717c49d322028a74648113bd8c9d7ec77cf3c89c1ec8718ceff8516d96b34c3c614f10699c9abc4ed0411506223bea16af35c883accdbe1104eef0cfdb54e12fb230a'.decode('hex')
-    >>> n = 'ff'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> cipher.encrypt(plain,n).encode('hex')
-    '3260ae8dad1f4a32c5cafe3ab0eb95549d461a67ceb9e5aa2d3afb62dece0553193ba50c75be251e08d1d08f1088576c7efdfaaf3f459559571e12511753b07af073f35da06af0ce0bbf6b8f5ccc5cea500ec1b211bd51f63b606bf6528796ca12173ba39b8935ee44ccce646f90a45bf9ccc567f0ace13dc2d53ebeedc81f58b2e41179dddf0d5a5c42f5d8506c1a5d2f8f59f3ea873cbcd0eec19acbf325423bd3dcb8c2b1bf1d1eaed0eba7f0698e4314fbeb2f1566d1b9253008cbccf45a2b0d9c5c9c21474f4076e02be26050b99dee4fd68a4cf890e496e4fcae7b70f94ea5a9062da0daeba1993d2ccd1dd3c244b8428801495a58b216547e7e847c46d1d756377b6242d2e5fb83bf752b54e0df71e889f3a2bb0f4c10805bf3c590376e3c24e22ff57f7fa965577375325cea5d920db94b9c336b455f6e894c01866fe9fbb8c8d3f70a2957285f6dfb5dcd8cbf54782f8fe7766d4723819913ac773421e3a31095866bad22c86a6036b2518b2059b4229d18c8c2ccbdf906c6cc6e82464ee57bddb0bebcb1dc645325bfb3e665ef7251082c88ebb1cf203bd779fdd38675713c8daadd17e1cabee432b09787b6ddf3304e38b731b45df5df51b78fcfb3d32466028d0ba36555e7e11ab0ee0666061d1645d962444bc47a38188930a84b4d561395c73c087021927ca638b7afc8a8679ccb84c26555440ec7f10445cd'
-
-    >>> key = ('2718281828459045235360287471352662497757247093699959574966967627'.decode('hex'),'3141592653589793238462643383279502884197169399375105820974944592'.decode('hex'))
-    >>> plain ='000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'.decode('hex')
-    >>> n = 'ffffffffff'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> ciphertext = cipher.encrypt(plain,n)
-    >>> ciphertext.encode('hex')
-    '64497e5a831e4a932c09be3e5393376daa599548b816031d224bbf50a818ed2350eae7e96087c8a0db51ad290bd00c1ac1620857635bf246c176ab463be30b808da548081ac847b158e1264be25bb0910bbc92647108089415d45fab1b3d2604e8a8eff1ae4020cfa39936b66827b23f371b92200be90251e6d73c5f86de5fd4a950781933d79a28272b782a2ec313efdfcc0628f43d744c2dc2ff3dcb66999b50c7ca895b0c64791eeaa5f29499fb1c026f84ce5b5c72ba1083cddb5ce45434631665c333b60b11593fb253c5179a2c8db813782a004856a1653011e93fb6d876c18366dd8683f53412c0c180f9c848592d593f8609ca736317d356e13e2bff3a9f59cd9aeb19cd482593d8c46128bb32423b37a9adfb482b99453fbe25a41bf6feb4aa0bef5ed24bf73c762978025482c13115e4015aac992e5613a3b5c2f685b84795cb6e9b2656d8c88157e52c42f978d8634c43d06fea928f2822e465aa6576e9bf419384506cc3ce3c54ac1a6f67dc66f3b30191e698380bc999b05abce19dc0c6dcc2dd001ec535ba18deb2df1a101023108318c75dc98611a09dc48a0acdec676fabdf222f07e026f059b672b56e5cbc8e1d21bbd867dd927212054681d70ea737134cdfce93b6f82ae22423274e58a0821cc5502e2d0ab4585e94de6975be5e0b4efce51cd3e70c25a1fbbbd609d273ad5b0d59631c531f6a0a57b9'
-    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> decipher.decrypt(ciphertext,n).encode('hex')
-    '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
-
-    using plaintext not a multiple of 16
-
-    >>> key = ('fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0'.decode('hex'),'bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0'.decode('hex'))
-    >>> plaintext = '000102030405060708090a0b0c0d0e0f10111213'.decode('hex')
-    >>> n = '9a78563412'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> ciphertext = cipher.encrypt(plaintext,n)
-    >>> ciphertext.encode('hex')
-    '9d84c813f719aa2c7be3f66171c7c5c2edbf9dac'
-    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> decipher.decrypt(ciphertext,n).encode('hex')
-    '000102030405060708090a0b0c0d0e0f10111213'
-
-    >>> key = ('fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0'.decode('hex'),'bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0'.decode('hex'))
-    >>> plaintext = '000102030405060708090a0b0c0d0e0f10'.decode('hex')
-    >>> n = '9a78563412'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> ciphertext = cipher.encrypt(plaintext,n)
-    >>> ciphertext.encode('hex')
-    '6c1625db4671522d3d7599601de7ca09ed'
-    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> decipher.decrypt(ciphertext,n).encode('hex')
-    '000102030405060708090a0b0c0d0e0f10'
-
-    >>> key = ('e0e1e2e3e4e5e6e7e8e9eaebecedeeef'.decode('hex'),'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf'.decode('hex'))
-    >>> plaintext = '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'.decode('hex')
-    >>> n = '21436587a9'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> ciphertext = cipher.encrypt(plaintext,n)
-    >>> ciphertext.encode('hex')
-    '38b45812ef43a05bd957e545907e223b954ab4aaf088303ad910eadf14b42be68b2461149d8c8ba85f992be970bc621f1b06573f63e867bf5875acafa04e42ccbd7bd3c2a0fb1fff791ec5ec36c66ae4ac1e806d81fbf709dbe29e471fad38549c8e66f5345d7c1eb94f405d1ec785cc6f6a68f6254dd8339f9d84057e01a17741990482999516b5611a38f41bb6478e6f173f320805dd71b1932fc333cb9ee39936beea9ad96fa10fb4112b901734ddad40bc1878995f8e11aee7d141a2f5d48b7a4e1e7f0b2c04830e69a4fd1378411c2f287edf48c6c4e5c247a19680f7fe41cefbd49b582106e3616cbbe4dfb2344b2ae9519391f3e0fb4922254b1d6d2d19c6d4d537b3a26f3bcc51588b32f3eca0829b6a5ac72578fb814fb43cf80d64a233e3f997a3f02683342f2b33d25b492536b93becb2f5e1a8b82f5b883342729e8ae09d16938841a21a97fb543eea3bbff59f13c1a18449e398701c1ad51648346cbc04c27bb2da3b93a1372ccae548fb53bee476f9e9c91773b1bb19828394d55d3e1a20ed69113a860b6829ffa847224604435070221b257e8dff783615d2cae4803a93aa4334ab482a0afac9c0aeda70b45a481df5dec5df8cc0f423c77a5fd46cd312021d4b438862419a791be03bb4d97c0e59578542531ba466a83baf92cefc151b5cc1611a167893819b63fb8a6b18e86de60290fa72b797b0ce59f3'
-    >>> decipher = python_AES.new(key,python_AES.MODE_XTS)
-    >>> decipher.decrypt(ciphertext,n).encode('hex')
-    '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
-
-    CMAC EXAMPLE:
-    -------------
-    NIST publication 800-38B: http://csrc.nist.gov/publications/nistpubs/800-38B/Updated_CMAC_Examples.pdf
-
-    >>> key = '2b7e151628aed2a6abf7158809cf4f3c'.decode('hex')
-    >>> plaintext = '6bc1bee22e409f96e93d7e117393172a'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_CMAC)
-    >>> cipher.encrypt(plaintext).encode('hex')[:16]
-    '070a16b46b4d4144'
-
-    CMAC EXAMPLE2:
-    --------------
-    >>> key = '2b7e151628aed2a6abf7158809cf4f3c'.decode('hex')
-    >>> plaintext = '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411'.decode('hex')
-    >>> cipher = python_AES.new(key,python_AES.MODE_CMAC)
-    >>> cipher.encrypt(plaintext).encode('hex')[:16]
-    'dfa66747de9ae630'
-    """
-    return AES(key,mode,IV,counter,segment_size)
-
-
-block_size = 16
-
-class AES(BlockCipher):
-    
-    key_error_message = ("Key should be 128, 192 or 256 bits")
-    block_size = 16
-    
-    def __init__(self,key,mode,IV,counter,segment_size):
-        cipher_module = rijndael
-        self.blocksize = 16
-        args = {'block_size':16}
-        
-        BlockCipher.__init__(self,key,mode,IV,counter,cipher_module,segment_size,args)
-
-    def keylen_valid(self,key):
-        return len(key) in (16,24,32)
-
-def _test():
-    import doctest
-    doctest.testmod()
-
-if __name__ == "__main__":
-    _test()
-

+ 0 - 0
pwman/util/crypto/__init__.py


+ 0 - 594
pwman/util/crypto/blockcipher.py

@@ -1,594 +0,0 @@
-# =============================================================================
-# Copyright (c) 2008 Christophe Oosterlynck <christophe.oosterlynck_AT_gmail.com>
-#                    & NXP ( Philippe Teuwen <philippe.teuwen_AT_nxp.com> )
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-# =============================================================================
-from . import util
-from . import padding
-import collections
-
-MODE_ECB = 1
-MODE_CBC = 2
-MODE_CFB = 3
-MODE_OFB = 5
-MODE_CTR = 6
-MODE_XTS = 7
-MODE_CMAC = 8
-
-class BlockCipher():
-    """ Base class for all blockciphers
-    """
-
-    key_error_message = "Wrong key size" #should be overwritten in child classes
-
-    def __init__(self,key,mode,IV,counter,cipher_module,segment_size,args={}):
-        # Cipher classes inheriting from this one take care of:
-        #   self.blocksize
-        #   self.cipher
-        self.key = key
-        self.mode = mode
-        self.cache = ''
-        self.ed = None
-
-        if 'keylen_valid' in dir(self): #wrappers for pycrypto functions don't have this function
-         if not self.keylen_valid(key) and type(key) is not tuple:
-                raise ValueError(self.key_error_message)
-
-        if IV == None:
-            self.IV = '\x00'*self.blocksize
-        else:
-            self.IV = IV
-
-        if mode != MODE_XTS:
-            self.cipher = cipher_module(self.key,**args)
-        if mode == MODE_ECB:
-            self.chain = ECB(self.cipher, self.blocksize)
-        elif mode == MODE_CBC:
-            if len(self.IV) != self.blocksize:
-                raise Exception("the IV length should be %i bytes"%self.blocksize)
-            self.chain = CBC(self.cipher, self.blocksize,self.IV)
-        elif mode == MODE_CFB:
-            if len(self.IV) != self.blocksize:
-                raise Exception("the IV length should be %i bytes"%self.blocksize)
-            if segment_size == None:
-                raise ValueError("segment size must be defined explicitely for CFB mode")
-            if segment_size > self.blocksize*8 or segment_size%8 != 0:
-                # current CFB implementation doesn't support bit level acces => segment_size should be multiple of bytes
-                raise ValueError("segment size should be a multiple of 8 bits between 8 and %i"%(self.blocksize*8))
-            self.chain = CFB(self.cipher, self.blocksize,self.IV,segment_size)
-        elif mode == MODE_OFB:
-            if len(self.IV) != self.blocksize:
-                raise ValueError("the IV length should be %i bytes"%self.blocksize)
-            self.chain = OFB(self.cipher, self.blocksize,self.IV)
-        elif mode == MODE_CTR:
-            if (counter == None) or  not isinstance(counter, collections.Callable):
-                raise Exception("Supply a valid counter object for the CTR mode")
-            self.chain = CTR(self.cipher,self.blocksize,counter)
-        elif mode == MODE_XTS:
-            if self.blocksize != 16:
-                raise Exception('XTS only works with blockcipher that have a 128-bit blocksize')
-            if not(type(key) == tuple and len(key) == 2):
-                raise Exception('Supply two keys as a tuple when using XTS')
-            if 'keylen_valid' in dir(self): #wrappers for pycrypto functions don't have this function
-             if not self.keylen_valid(key[0]) or  not self.keylen_valid(key[1]):
-                raise ValueError(self.key_error_message)
-            self.cipher = cipher_module(self.key[0],**args)
-            self.cipher2 = cipher_module(self.key[1],**args)
-            self.chain = XTS(self.cipher, self.cipher2)
-        elif mode == MODE_CMAC:
-            if self.blocksize not in (8,16):
-                raise Exception('CMAC only works with blockcipher that have a 64 or 128-bit blocksize')
-            self.chain = CMAC(self.cipher,self.blocksize,self.IV)
-        else:
-                raise Exception("Unknown chaining mode!")
-
-    def encrypt(self,plaintext,n=''):
-        """Encrypt some plaintext
-
-            plaintext   = a string of binary data
-            n           = the 'tweak' value when the chaining mode is XTS
-
-        The encrypt function will encrypt the supplied plaintext.
-        The behavior varies slightly depending on the chaining mode.
-
-        ECB, CBC:
-        ---------
-        When the supplied plaintext is not a multiple of the blocksize
-          of the cipher, then the remaining plaintext will be cached.
-        The next time the encrypt function is called with some plaintext,
-          the new plaintext will be concatenated to the cache and then
-          cache+plaintext will be encrypted.
-
-        CFB, OFB, CTR:
-        --------------
-        When the chaining mode allows the cipher to act as a stream cipher,
-          the encrypt function will always encrypt all of the supplied
-          plaintext immediately. No cache will be kept.
-
-        XTS:
-        ----
-        Because the handling of the last two blocks is linked,
-          it needs the whole block of plaintext to be supplied at once.
-        Every encrypt function called on a XTS cipher will output
-          an encrypted block based on the current supplied plaintext block.
-
-        CMAC:
-        -----
-        Everytime the function is called, the hash from the input data is calculated.
-        No finalizing needed.
-        The hashlength is equal to block size of the used block cipher.
-        """
-        #self.ed = 'e' if chain is encrypting, 'd' if decrypting,
-        # None if nothing happened with the chain yet
-        #assert self.ed in ('e',None) 
-        # makes sure you don't encrypt with a cipher that has started decrypting
-        self.ed = 'e'
-        if self.mode == MODE_XTS:
-            # data sequence number (or 'tweak') has to be provided when in XTS mode
-            return self.chain.update(plaintext,'e',n)
-        else:
-            return self.chain.update(plaintext,'e')
-
-    def decrypt(self,ciphertext,n=''):
-        """Decrypt some ciphertext
-
-            ciphertext  = a string of binary data
-            n           = the 'tweak' value when the chaining mode is XTS
-
-        The decrypt function will decrypt the supplied ciphertext.
-        The behavior varies slightly depending on the chaining mode.
-
-        ECB, CBC:
-        ---------
-        When the supplied ciphertext is not a multiple of the blocksize
-          of the cipher, then the remaining ciphertext will be cached.
-        The next time the decrypt function is called with some ciphertext,
-          the new ciphertext will be concatenated to the cache and then
-          cache+ciphertext will be decrypted.
-
-        CFB, OFB, CTR:
-        --------------
-        When the chaining mode allows the cipher to act as a stream cipher,
-          the decrypt function will always decrypt all of the supplied
-          ciphertext immediately. No cache will be kept.
-
-        XTS:
-        ----
-        Because the handling of the last two blocks is linked,
-          it needs the whole block of ciphertext to be supplied at once.
-        Every decrypt function called on a XTS cipher will output
-          a decrypted block based on the current supplied ciphertext block.
-
-        CMAC:
-        -----
-        Mode not supported for decryption as this does not make sense.
-        """
-        #self.ed = 'e' if chain is encrypting, 'd' if decrypting,
-        # None if nothing happened with the chain yet
-        #assert self.ed in ('d',None)
-        # makes sure you don't decrypt with a cipher that has started encrypting
-        self.ed = 'd'
-        if self.mode == MODE_XTS:
-            # data sequence number (or 'tweak') has to be provided when in XTS mode
-            return self.chain.update(ciphertext,'d',n)
-        else:
-            return self.chain.update(ciphertext,'d')
-
-    def final(self,padfct=padding.PKCS7):
-        # TODO: after calling final, reset the IV? so the cipher is as good as new?
-        """Finalizes the encryption by padding the cache
-
-            padfct = padding function
-                     import from CryptoPlus.Util.padding
-
-        For ECB, CBC: the remaining bytes in the cache will be padded and
-                      encrypted.
-        For OFB,CFB, CTR: an encrypted padding will be returned, making the
-                          total outputed bytes since construction of the cipher
-                          a multiple of the blocksize of that cipher.
-
-        If the cipher has been used for decryption, the final function won't do
-          anything. You have to manually unpad if necessary.
-
-        After finalization, the chain can still be used but the IV, counter etc
-          aren't reset but just continue as they were after the last step (finalization step).
-        """
-        assert self.mode not in (MODE_XTS, MODE_CMAC) # finalizing (=padding) doesn't make sense when in XTS or CMAC mode
-        if self.ed == 'e':
-            # when the chain is in encryption mode, finalizing will pad the cache and encrypt this last block
-            if self.mode in (MODE_OFB,MODE_CFB,MODE_CTR):
-                dummy = '0'*(self.chain.totalbytes%self.blocksize) # a dummy string that will be used to get a valid padding
-            else: #ECB, CBC
-                dummy = self.chain.cache
-            pad = padfct(dummy,padding.PAD,self.blocksize)[len(dummy):] # construct the padding necessary
-            return self.chain.update(pad,'e') # supply the padding to the update function => chain cache will be "cache+padding"
-        else:
-            # final function doesn't make sense when decrypting => padding should be removed manually
-            pass
-
-class ECB:
-    """ECB chaining mode
-    """
-    def __init__(self, codebook, blocksize):
-        self.cache = ''
-        self.codebook = codebook
-        self.blocksize = blocksize
-
-    def update(self, data, ed):
-        """Processes the given ciphertext/plaintext
-
-        Inputs:
-            data: raw string of any length
-            ed:   'e' for encryption, 'd' for decryption
-        Output:
-            processed raw string block(s), if any
-
-        When the supplied data is not a multiple of the blocksize
-          of the cipher, then the remaining input data will be cached.
-        The next time the update function is called with some data,
-          the new data will be concatenated to the cache and then
-          cache+data will be processed and full blocks will be outputted.
-        """
-        output_blocks = []
-        self.cache += data
-        if len(self.cache) < self.blocksize:
-            return ''
-        for i in range(0, len(self.cache)-self.blocksize+1, self.blocksize):
-            #the only difference between encryption/decryption in the chain is the cipher block
-            if ed == 'e':
-                output_blocks.append(self.codebook.encrypt( self.cache[i:i + self.blocksize] ))
-            else:
-                output_blocks.append(self.codebook.decrypt( self.cache[i:i + self.blocksize] ))
-        self.cache = self.cache[i+self.blocksize:]
-        return ''.join(output_blocks)
-
-class CBC:
-    """CBC chaining mode
-    """
-    def __init__(self, codebook, blocksize, IV):
-        self.IV = IV
-        self.cache = ''
-        self.codebook = codebook
-        self.blocksize = blocksize
-
-    def update(self, data, ed):
-        """Processes the given ciphertext/plaintext
-
-        Inputs:
-            data: raw string of any length
-            ed:   'e' for encryption, 'd' for decryption
-        Output:
-            processed raw string block(s), if any
-
-        When the supplied data is not a multiple of the blocksize
-          of the cipher, then the remaining input data will be cached.
-        The next time the update function is called with some data,
-          the new data will be concatenated to the cache and then
-          cache+data will be processed and full blocks will be outputted.
-        """
-        if ed == 'e':
-            encrypted_blocks = ''
-            self.cache += data
-            if len(self.cache) < self.blocksize:
-                return ''
-            for i in range(0, len(self.cache)-self.blocksize+1, self.blocksize):
-                self.IV = self.codebook.encrypt(util.xorstring(self.cache[i:i+self.blocksize],self.IV))
-                encrypted_blocks += self.IV
-            self.cache = self.cache[i+self.blocksize:]
-            return encrypted_blocks
-        else:
-            decrypted_blocks = ''
-            self.cache += data
-            if len(self.cache) < self.blocksize:
-                return ''
-            for i in range(0, len(self.cache)-self.blocksize+1, self.blocksize):
-                plaintext = util.xorstring(self.IV,self.codebook.decrypt(self.cache[i:i + self.blocksize]))
-                self.IV = self.cache[i:i + self.blocksize]
-                decrypted_blocks+=plaintext
-            self.cache = self.cache[i+self.blocksize:]
-            return decrypted_blocks
-
-class CFB:
-    # TODO: bit access instead of only byte level access
-    """CFB Chaining Mode
-
-    Can be accessed as a stream cipher.
-    """
-
-    def __init__(self, codebook, blocksize, IV,segment_size):
-        self.codebook = codebook
-        self.IV = IV
-        self.blocksize = blocksize
-        self.segment_size = segment_size/8
-        self.keystream = []
-        self.totalbytes = 0
-        
-    def update(self, data, ed):
-        """Processes the given ciphertext/plaintext
-
-        Inputs:
-            data: raw string of any multiple of bytes
-            ed:   'e' for encryption, 'd' for decryption
-        Output:
-            processed raw string
-
-        The encrypt/decrypt functions will always process all of the supplied
-          input data immediately. No cache will be kept.
-        """
-        output = list(data)
-
-        for i in range(len(data)):
-            if ed =='e':
-                if len(self.keystream) == 0:
-                    block = self.codebook.encrypt(self.IV)
-                    self.keystream = list(block)[:self.segment_size] # keystream consists of the s MSB's
-                    self.IV = self.IV[self.segment_size:] # keeping (b-s) LSB's
-                output[i] = chr(ord(output[i]) ^ ord(self.keystream.pop(0)))
-                self.IV += output[i] # the IV for the next block in the chain is being built byte per byte as the ciphertext flows in
-            else:
-                if len(self.keystream) == 0:
-                    block = self.codebook.encrypt(self.IV)
-                    self.keystream = list(block)[:self.segment_size]
-                    self.IV = self.IV[self.segment_size:]
-                self.IV += output[i]
-                output[i] = chr(ord(output[i]) ^ ord(self.keystream.pop(0)))
-        self.totalbytes += len(output)
-        return ''.join(output)
-
-class OFB:
-    """OFB Chaining Mode
-
-    Can be accessed as a stream cipher.
-    """
-    def __init__(self, codebook, blocksize, IV):
-        self.codebook = codebook
-        self.IV = IV
-        self.blocksize = blocksize
-        self.keystream = []
-        self.totalbytes = 0
-        
-    def update(self, data, ed):
-        """Processes the given ciphertext/plaintext
-
-        Inputs:
-            data: raw string of any multiple of bytes
-            ed:   'e' for encryption, 'd' for decryption
-        Output:
-            processed raw string
-
-        The encrypt/decrypt functions will always process all of the supplied
-          input data immediately. No cache will be kept.
-        """
-        #no difference between encryption and decryption mode
-        n = len(data)
-        blocksize = self.blocksize
-        output = list(data)
-
-        for i in range(n):
-            if len(self.keystream) == 0: #encrypt a new counter block when the current keystream is fully used
-                self.IV = self.codebook.encrypt(self.IV)
-                self.keystream = list(self.IV)
-            output[i] = chr(ord(output[i]) ^ ord(self.keystream.pop(0))) #as long as an encrypted counter value is available, the output is just "input XOR keystream"
-        
-        self.totalbytes += len(output)
-        return ''.join(output)
-
-class CTR:
-    """CTR Chaining Mode
-
-    Can be accessed as a stream cipher.
-    """
-    # initial counter value can be choosen, decryption always starts from beginning
-    #   -> you can start from anywhere yourself: just feed the cipher encoded blocks and feed a counter with the corresponding value
-    def __init__(self, codebook, blocksize, counter):
-        self.codebook = codebook
-        self.counter = counter
-        self.blocksize = blocksize
-        self.keystream = [] #holds the output of the current encrypted counter value
-        self.totalbytes = 0
-
-    def update(self, data, ed):
-        """Processes the given ciphertext/plaintext
-
-        Inputs:
-            data: raw string of any multiple of bytes
-            ed:   'e' for encryption, 'd' for decryption
-        Output:
-            processed raw string
-
-        The encrypt/decrypt functions will always process all of the supplied
-          input data immediately. No cache will be kept.
-        """
-        # no need for the encryption/decryption distinction: both are the same
-        n = len(data)
-        blocksize = self.blocksize
-
-        output = list(data)
-        for i in range(n):
-            if len(self.keystream) == 0: #encrypt a new counter block when the current keystream is fully used
-                block = self.codebook.encrypt(self.counter())
-                self.keystream = list(block)
-            output[i] = chr(ord(output[i])^ord(self.keystream.pop(0))) #as long as an encrypted counter value is available, the output is just "input XOR keystream"
-        self.totalbytes += len(output)
-        return ''.join(output)
-
-class XTS:
-    """XTS Chaining Mode
-    
-    Usable with blockciphers with a 16-byte blocksize
-    """
-    # TODO: allow other blocksizes besides 16bytes?
-    def __init__(self,codebook1, codebook2):
-        self.cache = ''
-        self.codebook1 = codebook1
-        self.codebook2 = codebook2
-
-    def update(self, data, ed,tweak=''):
-        # supply n as a raw string
-        # tweak = data sequence number
-        """Perform a XTS encrypt/decrypt operation.
-
-        Because the handling of the last two blocks is linked,
-          it needs the whole block of ciphertext to be supplied at once.
-        Every decrypt function called on a XTS cipher will output
-          a decrypted block based on the current supplied ciphertext block.
-        """
-        output = ''
-        assert len(data) > 15, "At least one block of 128 bits needs to be supplied"
-        assert len(data) < 128*pow(2,20)
-
-        # initializing T
-        # e_k2_n = E_K2(tweak)
-        e_k2_n = self.codebook2.encrypt(tweak+ '\x00' * (16-len(tweak)))[::-1]
-        self.T = util.string2number(e_k2_n)
-
-        i=0
-        while i < ((len(data) // 16)-1): #Decrypt all the blocks but one last full block and opt one last partial block
-            # C = E_K1(P xor T) xor T
-            output += self.__xts_step(ed,data[i*16:(i+1)*16],self.T)
-            # T = E_K2(n) mul (a pow i)
-            self.__T_update()
-            i+=1
-
-        # Check if the data supplied is a multiple of 16 bytes -> one last full block and we're done
-        if len(data[i*16:]) == 16:
-            # C = E_K1(P xor T) xor T
-            output += self.__xts_step(ed,data[i*16:(i+1)*16],self.T)
-            # T = E_K2(n) mul (a pow i)
-            self.__T_update()
-        else:
-            T_temp = [self.T]
-            self.__T_update()
-            T_temp.append(self.T)
-            if ed=='d':
-                # Permutation of the last two indexes
-                T_temp.reverse()
-            # Decrypt/Encrypt the last two blocks when data is not a multiple of 16 bytes
-            Cm1 = data[i*16:(i+1)*16]
-            Cm = data[(i+1)*16:]
-            PP = self.__xts_step(ed,Cm1,T_temp[0])
-            Cp = PP[len(Cm):]
-            Pm = PP[:len(Cm)]
-            CC = Cm+Cp
-            Pm1 = self.__xts_step(ed,CC,T_temp[1])
-            output += Pm1 + Pm
-        return output
-
-    def __xts_step(self,ed,tocrypt,T):
-        T_string = util.number2string_N(T,16)[::-1]
-        # C = E_K1(P xor T) xor T
-        if ed == 'd':
-            return util.xorstring(T_string, self.codebook1.decrypt(util.xorstring(T_string, tocrypt)))
-        else:
-            return util.xorstring(T_string, self.codebook1.encrypt(util.xorstring(T_string, tocrypt)))
-
-    def __T_update(self):
-        # Used for calculating T for a certain step using the T value from the previous step
-        self.T = self.T << 1
-        # if (Cout)
-        if self.T >> (8*16):
-            #T[0] ^= GF_128_FDBK;
-            self.T = self.T ^ 0x100000000000000000000000000000087
-
-
-class CMAC:
-    """CMAC chaining mode
-
-    Supports every cipher with a blocksize available
-      in the list CMAC.supported_blocksizes.
-    The hashlength is equal to block size of the used block cipher.
-    
-    Usable with blockciphers with a 8 or 16-byte blocksize
-    """
-    # TODO: move to hash module?
-    # TODO: change update behaviour to .update() and .digest() as for all hash modules?
-    #       -> other hash functions in pycrypto: calling update, concatenates current input with previous input and hashes everything
-    __Rb_dictionary = {64:0x000000000000001b,128:0x00000000000000000000000000000087}
-    supported_blocksizes = list(__Rb_dictionary.keys())
-    def __init__(self,codebook,blocksize,IV):
-        # Purpose of init: calculate Lu & Lu2
-        #blocksize (in bytes): to select the Rb constant in the dictionary
-        #Rb as a dictionary: adding support for other blocksizes is easy
-        self.cache=''
-        self.blocksize = blocksize
-        self.codebook = codebook
-        self.IV = IV
-
-        #Rb_dictionary: holds values for Rb for different blocksizes
-        # values for 64 and 128 bits found here: http://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html
-        # explanation from: http://csrc.nist.gov/publications/nistpubs/800-38B/SP_800-38B.pdf
-        #             Rb is a representation of a certain irreducible binary polynomial of degree b, namely,
-        #             the lexicographically first among all such polynomials with the minimum possible number of
-        #             nonzero terms. If this polynomial is expressed as ub+cb-1ub-1+...+c2u2+c1u+c0, where the
-        #             coefficients cb-1, cb-2, ..., c2, c1, c0 are either 0 or 1, then Rb is the bit string cb-1cb-2...c2c1c0.
-
-        self.Rb = self.__Rb_dictionary[blocksize*8]
-
-        mask1 = int(('\xff'*blocksize).encode('hex'),16)
-        mask2 = int(('\x80' + '\x00'*(blocksize-1) ).encode('hex'),16)
-
-        L = int(self.codebook.encrypt('\x00'*blocksize).encode('hex'),16)
-
-        if L & mask2:
-            Lu = ((L << 1) & mask1) ^ self.Rb
-        else:
-            Lu = L << 1
-            Lu = Lu & mask1
-
-        if Lu & mask2:
-            Lu2 = ((Lu << 1) & mask1)^ self.Rb
-        else:
-            Lu2 = Lu << 1
-            Lu2 = Lu2 & mask1
-
-        self.Lu =util.number2string_N(Lu,self.blocksize)
-        self.Lu2=util.number2string_N(Lu2,self.blocksize)
-
-    def update(self, data, ed):
-        """Processes the given ciphertext/plaintext
-
-        Inputs:
-            data: raw string of any length
-            ed:   'e' for encryption, 'd' for decryption
-        Output:
-            hashed data as raw string
-
-        This is not really an update function:
-        Everytime the function is called, the hash from the input data is calculated.
-        No finalizing needed.
-        """
-        assert ed == 'e'
-        blocksize = self.blocksize
-
-        m = (len(data)+blocksize-1)/blocksize #m = amount of datablocks
-        i=0
-        for i in range(1,m):
-            self.IV = self.codebook.encrypt( util.xorstring(data[(i-1)*blocksize:(i)*blocksize],self.IV) )
-
-        if len(data[(i)*blocksize:])==blocksize:
-            X = util.xorstring(util.xorstring(data[(i)*blocksize:],self.IV),self.Lu)
-        else:
-            tmp = data[(i)*blocksize:] + '\x80' + '\x00'*(blocksize - len(data[(i)*blocksize:])-1)
-            X = util.xorstring(util.xorstring(tmp,self.IV),self.Lu2)
-
-        T = self.codebook.encrypt(X)
-        return T
-
-

+ 0 - 248
pwman/util/crypto/padding.py

@@ -1,248 +0,0 @@
-# =============================================================================
-# Copyright (c) 2008 Christophe Oosterlynck <christophe.oosterlynck_AT_gmail.com>
-#                    & NXP ( Philippe Teuwen <philippe.teuwen_AT_nxp.com> )
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-# =============================================================================
-
-"""Module for padding functions
-
-padding info here: http://en.wikipedia.org/wiki/Padding_(cryptography)
-"""
-    
-import random
-
-PAD = 0
-UNPAD = 1
-
-def bitPadding (padData, direction, length=None):
-        """Pad a string using bitPadding
-
-            padData = raw string to pad/unpad
-            direction = PAD or UNPAD
-            length = amount of bytes the padded string should be a multiple of
-                     (length variable is not used when unpadding)
-            
-            returns: (un)padded raw string
-            
-            A new block full of padding will be added when padding data that is
-            already a multiple of the length.
-            
-            Example:
-            =========
-            >>> import padding
-
-            >>> padding.bitPadding('test', padding.PAD, 8)
-            'test\\x80\\x00\\x00\\x00'
-            >>> padding.bitPadding(_,padding.UNPAD)
-            'test'"""
-        if direction == PAD:
-            if length == None:
-                raise ValueError("Supply a valid length")
-            return __bitPadding(padData, length)
-        elif direction == UNPAD:
-            return __bitPadding_unpad(padData)
-        else:
-            raise ValueError("Supply a valid direction")
-
-def __bitPadding (toPad,length):
-    padded = toPad + '\x80' + '\x00'*(length - len(toPad)%length -1)
-    return padded
-
-def __bitPadding_unpad (padded):
-    if padded.rstrip('\x00')[-1] == '\x80':
-        return padded.rstrip('\x00')[:-1]
-    else:
-        return padded
-
-def zerosPadding (padData, direction, length=None):
-        """Pad a string using zerosPadding
-
-            padData = raw string to pad/unpad
-            direction = PAD or UNPAD
-                        beware: padding and unpadding a string ending in 0's
-                                will remove those 0's too
-            length = amount of bytes the padded string should be a multiple of
-                     (length variable is not used when unpadding)
-            
-            returns: (un)padded raw string
-            
-            No padding will be added when padding data that is already a
-            multiple of the given length.
-            
-            Example:
-            =========
-            >>> import padding
-
-            >>> padding.zerosPadding('12345678',padding.PAD,16)
-            '12345678\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'
-            >>> padding.zerosPadding(_,padding.UNPAD)
-            '12345678'"""
-        if direction == PAD:
-            if length == None:
-                raise ValueError("Supply a valid length")
-            return __zerosPadding(padData, length)
-        elif direction == UNPAD:
-            return __zerosPadding_unpad(padData)
-        else:
-            raise ValueError("Supply a valid direction")
-
-def __zerosPadding (toPad, length):
-    padLength = (length - len(toPad))%length
-    return toPad + '\x00'*padLength
-
-def __zerosPadding_unpad (padded ):
-    return padded.rstrip('\x00')
-
-def PKCS7(padData, direction, length=None):
-        """Pad a string using PKCS7
-
-            padData = raw string to pad/unpad
-            direction = PAD or UNPAD
-            length = amount of bytes the padded string should be a multiple of
-                     (length variable is not used when unpadding)
-            
-            returns: (un)padded raw string
-            
-            A new block full of padding will be added when padding data that is
-            already a multiple of the given length.
-            
-            Example:
-            =========
-            >>> import padding
-
-            >>> padding.PKCS7('12345678',padding.PAD,16)
-            '12345678\\x08\\x08\\x08\\x08\\x08\\x08\\x08\\x08'
-            >>> padding.PKCS7(_,padding.UNPAD)
-            '12345678'"""
-        if direction == PAD:
-            if length == None:
-                raise ValueError("Supply a valid length")
-            return __PKCS7(padData, length)
-        elif direction == UNPAD:
-            return __PKCS7_unpad(padData)
-        else:
-            raise ValueError("Supply a valid direction")
-
-
-def __PKCS7 (toPad, length):
-    amount = length - len(toPad)%length
-    pattern = chr(amount)
-    pad = pattern*amount
-    return toPad + pad
-
-def __PKCS7_unpad (padded):
-    pattern = padded[-1]
-    length = ord(pattern)
-    #check if the bytes to be removed are all the same pattern
-    if padded.endswith(pattern*length):
-        return padded[:-length]
-    else:
-        return padded
-        print('error: padding pattern not recognized')
-
-def ANSI_X923 (padData, direction, length=None):
-        """Pad a string using ANSI_X923
-
-            padData = raw string to pad/unpad
-            direction = PAD or UNPAD
-            length = amount of bytes the padded string should be a multiple of
-                     (length variable is not used when unpadding)
-            
-            returns: (un)padded raw string
-            
-            A new block full of padding will be added when padding data that is
-            already a multiple of the given length.
-            
-            Example:
-            =========
-            >>> import padding
-            
-            >>> padding.ANSI_X923('12345678',padding.PAD,16)
-            '12345678\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x08'
-            >>> padding.ANSI_X923(_,padding.UNPAD)
-            '12345678'"""
-        if direction == PAD:
-            if length == None:
-                raise ValueError("Supply a valid length")
-            return __ANSI_X923(padData, length)
-        elif direction == UNPAD:
-            return __ANSI_X923_unpad(padData)
-        else:
-            raise ValueError("Supply a valid direction")
-
-def __ANSI_X923 (toPad, length):
-    bytesToPad = length - len(toPad)%length
-    trail = chr(bytesToPad)
-    pattern = '\x00'*(bytesToPad -1) + trail
-    return toPad + pattern
-
-def __ANSI_X923_unpad (padded):
-    length =ord(padded[-1])
-    #check if the bytes to be removed are all zero
-    if padded.count('\x00',-length,-1) == length - 1:
-        return padded[:-length]
-    else:
-        print('error: padding pattern not recognized %s' % padded.count('\x00',-length,-1))
-        return padded
-
-def ISO_10126 (padData, direction, length=None):
-        """Pad a string using ISO_10126
-
-            padData = raw string to pad/unpad
-            direction = PAD or UNPAD
-            length = amount of bytes the padded string should be a multiple of
-                     (length variable is not used when unpadding)
-            
-            returns: (un)padded raw string
-            
-            A new block full of padding will be added when padding data that is
-            already a multiple of the given length.
-            
-            Example:
-            =========
-            >>> import padding
-
-            >>> padded = padding.ISO_10126('12345678',padding.PAD,16)
-            >>> padding.ISO_10126(padded,padding.UNPAD)
-            '12345678'"""
-        if direction == PAD:
-            if length == None:
-                raise ValueError("Supply a valid length")
-            return __ISO_10126(padData, length)
-        elif direction == UNPAD:
-            return __ISO_10126_unpad(padData)
-        else:
-            raise ValueError("Supply a valid direction")
-
-def __ISO_10126 (toPad, length):
-    bytesToPad = length - len(toPad)%length
-    randomPattern = ''.join(chr(random.randint(0,255)) for x in range(0,bytesToPad-1))
-    return toPad + randomPattern + chr(bytesToPad)
-
-def __ISO_10126_unpad (padded):
-   return padded[0:len(padded)-ord(padded[-1])]
-
-def _test():
-    import doctest
-    doctest.testmod()
-
-if __name__ == "__main__":
-    _test()
-

+ 0 - 354
pwman/util/crypto/pypbkdf2.py

@@ -1,354 +0,0 @@
-#!/usr/bin/python
-# -*- coding: ascii -*-
-###########################################################################
-# PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation
-#
-# Copyright (C) 2007, 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
-# All rights reserved.
-# 
-# Permission to use, copy, modify, and distribute this software and its
-# documentation for any purpose and without fee is hereby granted,
-# provided that the above copyright notice appear in all copies and that
-# both that copyright notice and this permission notice appear in
-# supporting documentation.
-# 
-# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR 
-# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
-# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
-# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-# Country of origin: Canada
-#
-###########################################################################
-# Sample PBKDF2 usage:
-#   from Crypto.Cipher import AES
-#   from PBKDF2 import PBKDF2
-#   import os
-#
-#   salt = os.urandom(8)    # 64-bit salt
-#   key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
-#   iv = os.urandom(16)     # 128-bit IV
-#   cipher = AES.new(key, AES.MODE_CBC, iv)
-#     ...
-#
-# Sample crypt() usage:
-#   from PBKDF2 import crypt
-#   pwhash = crypt("secret")
-#   alleged_pw = raw_input("Enter password: ")
-#   if pwhash == crypt(alleged_pw, pwhash):
-#       print "Password good"
-#   else:
-#       print "Invalid password"
-#
-###########################################################################
-# History:
-#
-#  2007-07-27 Dwayne C. Litzenberger <dlitz@dlitz.net>
-#   - Initial Release (v1.0)
-#
-#  2007-07-31 Dwayne C. Litzenberger <dlitz@dlitz.net>
-#   - Bugfix release (v1.1)
-#   - SECURITY: The PyCrypto XOR cipher (used, if available, in the _strxor
-#   function in the previous release) silently truncates all keys to 64
-#   bytes.  The way it was used in the previous release, this would only be
-#   problem if the pseudorandom function that returned values larger than
-#   64 bytes (so SHA1, SHA256 and SHA512 are fine), but I don't like
-#   anything that silently reduces the security margin from what is
-#   expected.
-#  
-# 2008-06-17 Dwayne C. Litzenberger <dlitz@dlitz.net>
-#   - Compatibility release (v1.2)
-#   - Add support for older versions of Python (2.2 and 2.3).
-#
-###########################################################################
-
-__version__ = "1.2"
-
-from struct import pack
-from binascii import b2a_hex
-from random import randint
-import string
-
-try:
-    # Use PyCrypto (if available)
-    from Crypto.Hash import HMAC, SHA as SHA1
-
-except ImportError:
-    # PyCrypto not available.  Use the Python standard library.
-    import hmac as HMAC
-    import sha as SHA1
-
-def strxor(a, b):
-    return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
-
-def b64encode(data, chars="+/"):
-    tt = string.maketrans("+/", chars)
-    return data.encode('base64').replace("\n", "").translate(tt)
-
-class PBKDF2(object):
-    """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
-    
-    This implementation takes a passphrase and a salt (and optionally an
-    iteration count, a digest module, and a MAC module) and provides a
-    file-like object from which an arbitrarily-sized key can be read.
-
-    If the passphrase and/or salt are unicode objects, they are encoded as
-    UTF-8 before they are processed.
-
-    The idea behind PBKDF2 is to derive a cryptographic key from a
-    passphrase and a salt.
-    
-    PBKDF2 may also be used as a strong salted password hash.  The
-    'crypt' function is provided for that purpose.
-    
-    Remember: Keys generated using PBKDF2 are only as strong as the
-    passphrases they are derived from.
-    """
-
-    def __init__(self, passphrase, salt, iterations=1000,
-                 digestmodule=SHA1, macmodule=HMAC):
-        self.__macmodule = macmodule
-        self.__digestmodule = digestmodule
-        self._setup(passphrase, salt, iterations, self._pseudorandom)
-
-    def _pseudorandom(self, key, msg):
-        """Pseudorandom function.  e.g. HMAC-SHA1"""
-        return self.__macmodule.new(key=key, msg=msg,
-            digestmod=self.__digestmodule).digest()
-    
-    def read(self, bytes):
-        """Read the specified number of key bytes."""
-        if self.closed:
-            raise ValueError("file-like object is closed")
-
-        size = len(self.__buf)
-        blocks = [self.__buf]
-        i = self.__blockNum
-        while size < bytes:
-            i += 1
-            if i > 0xffffffffL or i < 1:
-                # We could return "" here, but 
-                raise OverflowError("derived key too long")
-            block = self.__f(i)
-            blocks.append(block)
-            size += len(block)
-        buf = "".join(blocks)
-        retval = buf[:bytes]
-        self.__buf = buf[bytes:]
-        self.__blockNum = i
-        return retval
-    
-    def __f(self, i):
-        # i must fit within 32 bits
-        assert 1 <= i <= 0xffffffffL
-        U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
-        result = U
-        for j in xrange(2, 1+self.__iterations):
-            U = self.__prf(self.__passphrase, U)
-            result = strxor(result, U)
-        return result
-    
-    def hexread(self, octets):
-        """Read the specified number of octets. Return them as hexadecimal.
-
-        Note that len(obj.hexread(n)) == 2*n.
-        """
-        return b2a_hex(self.read(octets))
-
-    def _setup(self, passphrase, salt, iterations, prf):
-        # Sanity checks:
-        
-        # passphrase and salt must be str or unicode (in the latter
-        # case, we convert to UTF-8)
-        if isinstance(passphrase, unicode):
-            passphrase = passphrase.encode("UTF-8")
-        if not isinstance(passphrase, str):
-            raise TypeError("passphrase must be str or unicode")
-        if isinstance(salt, unicode):
-            salt = salt.encode("UTF-8")
-        if not isinstance(salt, str):
-            raise TypeError("salt must be str or unicode")
-
-        # iterations must be an integer >= 1
-        if not isinstance(iterations, (int, long)):
-            raise TypeError("iterations must be an integer")
-        if iterations < 1:
-            raise ValueError("iterations must be at least 1")
-        
-        # prf must be callable
-        if not callable(prf):
-            raise TypeError("prf must be callable")
-
-        self.__passphrase = passphrase
-        self.__salt = salt
-        self.__iterations = iterations
-        self.__prf = prf
-        self.__blockNum = 0
-        self.__buf = ""
-        self.closed = False
-    
-    def close(self):
-        """Close the stream."""
-        if not self.closed:
-            del self.__passphrase
-            del self.__salt
-            del self.__iterations
-            del self.__prf
-            del self.__blockNum
-            del self.__buf
-            self.closed = True
-
-def crypt(word, salt=None, iterations=None):
-    """PBKDF2-based unix crypt(3) replacement.
-    
-    The number of iterations specified in the salt overrides the 'iterations'
-    parameter.
-
-    The effective hash length is 192 bits.
-    """
-    
-    # Generate a (pseudo-)random salt if the user hasn't provided one.
-    if salt is None:
-        salt = _makesalt()
-
-    # salt must be a string or the us-ascii subset of unicode
-    if isinstance(salt, unicode):
-        salt = salt.encode("us-ascii")
-    if not isinstance(salt, str):
-        raise TypeError("salt must be a string")
-
-    # word must be a string or unicode (in the latter case, we convert to UTF-8)
-    if isinstance(word, unicode):
-        word = word.encode("UTF-8")
-    if not isinstance(word, str):
-        raise TypeError("word must be a string or unicode")
-
-    # Try to extract the real salt and iteration count from the salt
-    if salt.startswith("$p5k2$"):
-        (iterations, salt, dummy) = salt.split("$")[2:5]
-        if iterations == "":
-            iterations = 400
-        else:
-            converted = int(iterations, 16)
-            if iterations != "%x" % converted:  # lowercase hex, minimum digits
-                raise ValueError("Invalid salt")
-            iterations = converted
-            if not (iterations >= 1):
-                raise ValueError("Invalid salt")
-    
-    # Make sure the salt matches the allowed character set
-    allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
-    for ch in salt:
-        if ch not in allowed:
-            raise ValueError("Illegal character %r in salt" % (ch,))
-
-    if iterations is None or iterations == 400:
-        iterations = 400
-        salt = "$p5k2$$" + salt
-    else:
-        salt = "$p5k2$%x$%s" % (iterations, salt)
-    rawhash = PBKDF2(word, salt, iterations).read(24)
-    return salt + "$" + b64encode(rawhash, "./")
-
-# Add crypt as a static method of the PBKDF2 class
-# This makes it easier to do "from PBKDF2 import PBKDF2" and still use
-# crypt.
-PBKDF2.crypt = staticmethod(crypt)
-
-def _makesalt():
-    """Return a 48-bit pseudorandom salt for crypt().
-    
-    This function is not suitable for generating cryptographic secrets.
-    """
-    binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
-    return b64encode(binarysalt, "./")
-
-def test_pbkdf2():
-    """Module self-test"""
-    from binascii import a2b_hex
-    
-    #
-    # Test vectors from RFC 3962
-    #
-
-    # Test 1
-    result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16)
-    expected = a2b_hex("cdedb5281bb2f801565a1122b2563515")
-    if result != expected:
-        raise RuntimeError("self-test failed")
-
-    # Test 2
-    result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32)
-    expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b"
-                "a7e52ddbc5e5142f708a31e2e62b1e13")
-    if result != expected:
-        raise RuntimeError("self-test failed")
-
-    # Test 3
-    result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32)
-    expected = ("139c30c0966bc32ba55fdbf212530ac9"
-                "c5ec59f1a452f5cc9ad940fea0598ed1")
-    if result != expected:
-        raise RuntimeError("self-test failed")
-    
-    # Test 4
-    result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32)
-    expected = ("9ccad6d468770cd51b10e6a68721be61"
-                "1a8b4d282601db3b36be9246915ec82a")
-    if result != expected:
-        raise RuntimeError("self-test failed")
-    
-    #
-    # Other test vectors
-    #
-    
-    # Chunked read
-    f = PBKDF2("kickstart", "workbench", 256)
-    result = f.read(17)
-    result += f.read(17)
-    result += f.read(1)
-    result += f.read(2)
-    result += f.read(3)
-    expected = PBKDF2("kickstart", "workbench", 256).read(40)
-    if result != expected:
-        raise RuntimeError("self-test failed")
-    
-    #
-    # crypt() test vectors
-    #
-
-    # crypt 1
-    result = crypt("cloadm", "exec")
-    expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'
-    if result != expected:
-        raise RuntimeError("self-test failed")
-    
-    # crypt 2
-    result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....')
-    expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'
-    if result != expected:
-        raise RuntimeError("self-test failed")
-
-    # crypt 3
-    result = crypt("dcl", "tUsch7fU", iterations=13)
-    expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL"
-    if result != expected:
-        raise RuntimeError("self-test failed")
-    
-    # crypt 4 (unicode)
-    result = crypt(u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2',
-        '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
-    expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
-    if result != expected:
-        raise RuntimeError("self-test failed")
-
-if __name__ == '__main__':
-    test_pbkdf2()
-
-# vim:set ts=4 sw=4 sts=4 expandtab:

+ 0 - 417
pwman/util/crypto/rijndael.py

@@ -1,417 +0,0 @@
-"""
-Copyright (c) 2014  Philippe Teuwen <phil@teuwen.org>
-
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of 
-this software and associated documentation files (the "Software"), to deal in 
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
-IN THE SOFTWARE.
-
-This code is taken from https://github.com/doegox/python-cryptoplus/
-
-A pure python (slow) implementation of rijndael with a decent interface
-
-To include -
-
-from rijndael import rijndael
-
-To do a key setup -
-
-r = rijndael(key, block_size = 16)
-
-key must be a string of length 16, 24, or 32
-blocksize must be 16, 24, or 32. Default is 16
-
-To use -
-
-ciphertext = r.encrypt(plaintext)
-plaintext = r.decrypt(ciphertext)
-
-If any strings are of the wrong length a ValueError is thrown
-
-
-
-"""
-
-# ported from the Java reference code by Bram Cohen, bram@gawth.com, April 2001
-# this code is public domain, unless someone makes
-# an intellectual property claim against the reference
-# code, in which case it can be made public domain by
-# deleting all the comments and renaming all the variables
-
-import copy
-import string
-
-
-
-#-----------------------
-#TREV - ADDED BECAUSE THERE'S WARNINGS ABOUT INT OVERFLOW BEHAVIOR CHANGING IN
-#2.4.....
-import os
-if os.name != "java":
-    import exceptions
-    if hasattr(exceptions, "FutureWarning"):
-        import warnings
-        warnings.filterwarnings("ignore", category=FutureWarning, append=1)
-#-----------------------
-
-
-
-shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]],
-          [[0, 0], [1, 5], [2, 4], [3, 3]],
-          [[0, 0], [1, 7], [3, 5], [4, 4]]]
-
-# [keysize][block_size]
-num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}}
-
-A = [[1, 1, 1, 1, 1, 0, 0, 0],
-     [0, 1, 1, 1, 1, 1, 0, 0],
-     [0, 0, 1, 1, 1, 1, 1, 0],
-     [0, 0, 0, 1, 1, 1, 1, 1],
-     [1, 0, 0, 0, 1, 1, 1, 1],
-     [1, 1, 0, 0, 0, 1, 1, 1],
-     [1, 1, 1, 0, 0, 0, 1, 1],
-     [1, 1, 1, 1, 0, 0, 0, 1]]
-
-# produce log and alog tables, needed for multiplying in the
-# field GF(2^m) (generator = 3)
-alog = [1]
-for i in range(255):
-    j = (alog[-1] << 1) ^ alog[-1]
-    if j & 0x100 != 0:
-        j ^= 0x11B
-    alog.append(j)
-
-log = [0] * 256
-for i in range(1, 255):
-    log[alog[i]] = i
-
-# multiply two elements of GF(2^m)
-def mul(a, b):
-    if a == 0 or b == 0:
-        return 0
-    return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255]
-
-# substitution box based on F^{-1}(x)
-box = [[0] * 8 for i in range(256)]
-box[1][7] = 1
-for i in range(2, 256):
-    j = alog[255 - log[i]]
-    for t in range(8):
-        box[i][t] = (j >> (7 - t)) & 0x01
-
-B = [0, 1, 1, 0, 0, 0, 1, 1]
-
-# affine transform:  box[i] <- B + A*box[i]
-cox = [[0] * 8 for i in range(256)]
-for i in range(256):
-    for t in range(8):
-        cox[i][t] = B[t]
-        for j in range(8):
-            cox[i][t] ^= A[t][j] * box[i][j]
-
-# S-boxes and inverse S-boxes
-S =  [0] * 256
-Si = [0] * 256
-for i in range(256):
-    S[i] = cox[i][0] << 7
-    for t in range(1, 8):
-        S[i] ^= cox[i][t] << (7-t)
-    Si[S[i] & 0xFF] = i
-
-# T-boxes
-G = [[2, 1, 1, 3],
-    [3, 2, 1, 1],
-    [1, 3, 2, 1],
-    [1, 1, 3, 2]]
-
-AA = [[0] * 8 for i in range(4)]
-
-for i in range(4):
-    for j in range(4):
-        AA[i][j] = G[i][j]
-        AA[i][i+4] = 1
-
-for i in range(4):
-    pivot = AA[i][i]
-    if pivot == 0:
-        t = i + 1
-        while AA[t][i] == 0 and t < 4:
-            t += 1
-            assert t != 4, 'G matrix must be invertible'
-            for j in range(8):
-                AA[i][j], AA[t][j] = AA[t][j], AA[i][j]
-            pivot = AA[i][i]
-    for j in range(8):
-        if AA[i][j] != 0:
-            AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255]
-    for t in range(4):
-        if i != t:
-            for j in range(i+1, 8):
-                AA[t][j] ^= mul(AA[i][j], AA[t][i])
-            AA[t][i] = 0
-
-iG = [[0] * 4 for i in range(4)]
-
-for i in range(4):
-    for j in range(4):
-        iG[i][j] = AA[i][j + 4]
-
-def mul4(a, bs):
-    if a == 0:
-        return 0
-    r = 0
-    for b in bs:
-        r <<= 8
-        if b != 0:
-            r = r | mul(a, b)
-    return r
-
-T1 = []
-T2 = []
-T3 = []
-T4 = []
-T5 = []
-T6 = []
-T7 = []
-T8 = []
-U1 = []
-U2 = []
-U3 = []
-U4 = []
-
-for t in range(256):
-    s = S[t]
-    T1.append(mul4(s, G[0]))
-    T2.append(mul4(s, G[1]))
-    T3.append(mul4(s, G[2]))
-    T4.append(mul4(s, G[3]))
-
-    s = Si[t]
-    T5.append(mul4(s, iG[0]))
-    T6.append(mul4(s, iG[1]))
-    T7.append(mul4(s, iG[2]))
-    T8.append(mul4(s, iG[3]))
-
-    U1.append(mul4(t, iG[0]))
-    U2.append(mul4(t, iG[1]))
-    U3.append(mul4(t, iG[2]))
-    U4.append(mul4(t, iG[3]))
-
-# round constants
-rcon = [1]
-r = 1
-for t in range(1, 30):
-    r = mul(2, r)
-    rcon.append(r)
-
-del A
-del AA
-del pivot
-del B
-del G
-del box
-del log
-del alog
-del i
-del j
-del r
-del s
-del t
-del mul
-del mul4
-del cox
-del iG
-
-class rijndael:
-    def __init__(self, key, block_size = 16):
-        if block_size != 16 and block_size != 24 and block_size != 32:
-            raise ValueError('Invalid block size: ' + str(block_size))
-        if len(key) != 16 and len(key) != 24 and len(key) != 32:
-            raise ValueError('Invalid key size: ' + str(len(key)))
-        self.block_size = block_size
-
-        ROUNDS = num_rounds[len(key)][block_size]
-        BC = block_size / 4
-        # encryption round keys
-        Ke = [[0] * BC for i in range(ROUNDS + 1)]
-        # decryption round keys
-        Kd = [[0] * BC for i in range(ROUNDS + 1)]
-        ROUND_KEY_COUNT = (ROUNDS + 1) * BC
-        KC = len(key) / 4
-
-        # copy user material bytes into temporary ints
-        tk = []
-        for i in range(0, KC):
-            tk.append((ord(key[i * 4]) << 24) | (ord(key[i * 4 + 1]) << 16) |
-                (ord(key[i * 4 + 2]) << 8) | ord(key[i * 4 + 3]))
-
-        # copy values into round key arrays
-        t = 0
-        j = 0
-        while j < KC and t < ROUND_KEY_COUNT:
-            Ke[t / BC][t % BC] = tk[j]
-            Kd[ROUNDS - (t / BC)][t % BC] = tk[j]
-            j += 1
-            t += 1
-        tt = 0
-        rconpointer = 0
-        while t < ROUND_KEY_COUNT:
-            # extrapolate using phi (the round key evolution function)
-            tt = tk[KC - 1]
-            tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^  \
-                     (S[(tt >>  8) & 0xFF] & 0xFF) << 16 ^  \
-                     (S[ tt        & 0xFF] & 0xFF) <<  8 ^  \
-                     (S[(tt >> 24) & 0xFF] & 0xFF)       ^  \
-                     (rcon[rconpointer]    & 0xFF) << 24
-            rconpointer += 1
-            if KC != 8:
-                for i in range(1, KC):
-                    tk[i] ^= tk[i-1]
-            else:
-                for i in range(1, KC / 2):
-                    tk[i] ^= tk[i-1]
-                tt = tk[KC / 2 - 1]
-                tk[KC / 2] ^= (S[ tt        & 0xFF] & 0xFF)       ^ \
-                              (S[(tt >>  8) & 0xFF] & 0xFF) <<  8 ^ \
-                              (S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \
-                              (S[(tt >> 24) & 0xFF] & 0xFF) << 24
-                for i in range(KC / 2 + 1, KC):
-                    tk[i] ^= tk[i-1]
-            # copy values into round key arrays
-            j = 0
-            while j < KC and t < ROUND_KEY_COUNT:
-                Ke[t / BC][t % BC] = tk[j]
-                Kd[ROUNDS - (t / BC)][t % BC] = tk[j]
-                j += 1
-                t += 1
-        # inverse MixColumn where needed
-        for r in range(1, ROUNDS):
-            for j in range(BC):
-                tt = Kd[r][j]
-                Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \
-                           U2[(tt >> 16) & 0xFF] ^ \
-                           U3[(tt >>  8) & 0xFF] ^ \
-                           U4[ tt        & 0xFF]
-        self.Ke = Ke
-        self.Kd = Kd
-
-    def encrypt(self, plaintext):
-        if len(plaintext) != self.block_size:
-            raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext)))
-        Ke = self.Ke
-
-        BC = self.block_size / 4
-        ROUNDS = len(Ke) - 1
-        if BC == 4:
-            SC = 0
-        elif BC == 6:
-            SC = 1
-        else:
-            SC = 2
-        s1 = shifts[SC][1][0]
-        s2 = shifts[SC][2][0]
-        s3 = shifts[SC][3][0]
-        a = [0] * BC
-        # temporary work array
-        t = []
-        # plaintext to ints + key
-        for i in range(BC):
-            t.append((ord(plaintext[i * 4    ]) << 24 |
-                      ord(plaintext[i * 4 + 1]) << 16 |
-                      ord(plaintext[i * 4 + 2]) <<  8 |
-                      ord(plaintext[i * 4 + 3])        ) ^ Ke[0][i])
-        # apply round transforms
-        for r in range(1, ROUNDS):
-            for i in range(BC):
-                a[i] = (T1[(t[ i           ] >> 24) & 0xFF] ^
-                        T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^
-                        T3[(t[(i + s2) % BC] >>  8) & 0xFF] ^
-                        T4[ t[(i + s3) % BC]        & 0xFF]  ) ^ Ke[r][i]
-            t = copy.copy(a)
-        # last round is special
-        result = []
-        for i in range(BC):
-            tt = Ke[ROUNDS][i]
-            result.append((S[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
-            result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
-            result.append((S[(t[(i + s2) % BC] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)
-            result.append((S[ t[(i + s3) % BC]        & 0xFF] ^  tt       ) & 0xFF)
-        return string.join(list(map(chr, result)), '')
-
-    def decrypt(self, ciphertext):
-        if len(ciphertext) != self.block_size:
-            raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext)))
-        Kd = self.Kd
-
-        BC = self.block_size / 4
-        ROUNDS = len(Kd) - 1
-        if BC == 4:
-            SC = 0
-        elif BC == 6:
-            SC = 1
-        else:
-            SC = 2
-        s1 = shifts[SC][1][1]
-        s2 = shifts[SC][2][1]
-        s3 = shifts[SC][3][1]
-        a = [0] * BC
-        # temporary work array
-        t = [0] * BC
-        # ciphertext to ints + key
-        for i in range(BC):
-            t[i] = (ord(ciphertext[i * 4    ]) << 24 |
-                    ord(ciphertext[i * 4 + 1]) << 16 |
-                    ord(ciphertext[i * 4 + 2]) <<  8 |
-                    ord(ciphertext[i * 4 + 3])        ) ^ Kd[0][i]
-        # apply round transforms
-        for r in range(1, ROUNDS):
-            for i in range(BC):
-                a[i] = (T5[(t[ i           ] >> 24) & 0xFF] ^
-                        T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^
-                        T7[(t[(i + s2) % BC] >>  8) & 0xFF] ^
-                        T8[ t[(i + s3) % BC]        & 0xFF]  ) ^ Kd[r][i]
-            t = copy.copy(a)
-        # last round is special
-        result = []
-        for i in range(BC):
-            tt = Kd[ROUNDS][i]
-            result.append((Si[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
-            result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
-            result.append((Si[(t[(i + s2) % BC] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)
-            result.append((Si[ t[(i + s3) % BC]        & 0xFF] ^  tt       ) & 0xFF)
-        return string.join(list(map(chr, result)), '')
-
-def encrypt(key, block):
-    return rijndael(key, len(block)).encrypt(block)
-
-def decrypt(key, block):
-    return rijndael(key, len(block)).decrypt(block)
-
-def test():
-    def t(kl, bl):
-        b = 'b' * bl
-        r = rijndael('a' * kl, bl)
-        assert r.decrypt(r.encrypt(b)) == b
-    t(16, 16)
-    t(16, 24)
-    t(16, 32)
-    t(24, 16)
-    t(24, 24)
-    t(24, 32)
-    t(32, 16)
-    t(32, 24)
-    t(32, 32)

+ 0 - 81
pwman/util/crypto/util.py

@@ -1,81 +0,0 @@
-"""
-Copyright (c) 2014  Philippe Teuwen <phil@teuwen.org>
-
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of 
-this software and associated documentation files (the "Software"), to deal in 
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
-IN THE SOFTWARE.
-
-This code is taken from https://github.com/doegox/python-cryptoplus/
-"""
-
-def number2string(i):
-    """Convert a number to a string
-
-    Input: long or integer
-    Output: string (big-endian)
-    """
-    s=hex(i)[2:].rstrip('L')
-    if len(s) % 2:
-        s = '0' + s
-    return s.decode('hex')
-
-def number2string_N(i, N):
-    """Convert a number to a string of fixed size
-
-    i: long or integer
-    N: length of string
-    Output: string (big-endian)
-    """
-    s = '%0*x' % (N*2, i)
-    return s.decode('hex')
-
-def string2number(i):
-    """ Convert a string to a number
-
-    Input: string (big-endian)
-    Output: long or integer
-    """
-    return int(i.encode('hex'),16)
-
-def xorstring(a,b):
-    """XOR two strings of same length
-
-    For more complex cases, see CryptoPlus.Cipher.XOR"""
-    assert len(a) == len(b)
-    return number2string_N(string2number(a)^string2number(b), len(a))
-
-class Counter(str):
-    #found here: http://www.lag.net/pipermail/paramiko/2008-February.txt
-    """Necessary for CTR chaining mode
-
-    Initializing a counter object (ctr = Counter('xxx'), gives a value to the counter object.
-    Everytime the object is called ( ctr() ) it returns the current value and increments it by 1.
-    Input/output is a raw string.
-
-    Counter value is big endian"""
-    def __init__(self, initial_ctr):
-        if not isinstance(initial_ctr, str):
-            raise TypeError("nonce must be str")
-        self.c = int(initial_ctr.encode('hex'), 16)
-    def __call__(self):
-        # This might be slow, but it works as a demonstration
-        ctr = ("%032x" % (self.c,)).decode('hex')
-        self.c += 1
-        return ctr
-
-

+ 37 - 31
pwman/util/crypto_engine.py

@@ -14,7 +14,7 @@
 # along with Pwman3; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 # ============================================================================
-# Copyright (C) 2014 Oz Nahum <nahumoz@gmail.com>
+# Copyright (C) 2016 Oz Nahum <nahumoz@gmail.com>
 # ============================================================================
 
 from __future__ import print_function
@@ -27,25 +27,29 @@ import string
 import sys
 import time
 
-try:
-    from Crypto.Cipher import AES
-    from Crypto.Protocol.KDF import PBKDF2
-except ImportError:
-    # PyCryptop not found, we use a compatible implementation
-    # in pure Python.
-    # This is good for Windows where software installation suck
-    # or embeded devices where compilation is a bit harder
-    from pwman.util.crypto import AES
-    from pwman.util.crypto.pypbkdf2 import PBKDF2
-
+from cryptography.fernet import Fernet
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
 
 from pwman.util.callback import Callback
 
 if sys.version_info.major > 2:  # pragma: no cover
     raw_input = input
 
-EncodeAES = lambda c, s: base64.b64encode(c.encrypt(s))
-DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip()
+
+def encode_AES(cipher, clear_text):
+    if not isinstance(clear_text, bytes):
+        clear_text = clear_text.encode()
+    return base64.b64encode(cipher.encrypt(clear_text))
+
+
+def decode_AES(cipher, encoded_text):
+    if not isinstance(encoded_text, bytes):
+        encoded_text = encoded_text.encode()
+
+    encoded_text = base64.b64decode(encoded_text)
+    return cipher.decrypt(encoded_text).rstrip()
 
 
 def generate_password(pass_len=8, uppercase=True, lowercase=True, digits=True,
@@ -83,23 +87,23 @@ def get_digest(password, salt):
     """
     Get a digest based on clear text password
     """
-    iterations = 5000
-    if isinstance(password, bytes):
-        password = password.decode()
-    try:
-        return PBKDF2(password, salt, dkLen=32, count=iterations)
-    except TypeError:
-        return PBKDF2(password, salt, iterations=iterations).read(32)
+    kdf = PBKDF2HMAC(
+        algorithm=hashes.SHA256(),
+        length=32,
+        salt=salt,
+        iterations=5000,
+        backend=default_backend()
+    )
+    key = base64.urlsafe_b64encode(kdf.derive(password))
+    return key
 
 
 def get_cipher(password, salt):
     """
     Create a chiper object from a hashed password
     """
-    iv = os.urandom(AES.block_size)
     dig = get_digest(password, salt)
-    chiper = AES.new(dig, AES.MODE_ECB, iv)
-    return chiper
+    return Fernet(dig)
 
 
 def prepare_data(text, block_size):
@@ -159,10 +163,11 @@ class CryptoEngine(object):  # pagma: no cover
         salt = self._salt
         tries = 0
         while tries < 5:
-            password = self._getsecret("Please type in your master password"
-                                       ).encode('utf-8')
-            if self.authenticate(password):
-                return password, salt
+            passwd = self._getsecret("Please type in your master password")
+            if not isinstance(passwd, bytes):
+                passwd = passwd.encode()
+            if self.authenticate(passwd):
+                return passwd, salt
 
             print("You entered a wrong password...")
             tries += 1
@@ -175,7 +180,7 @@ class CryptoEngine(object):  # pagma: no cover
             self._cipher = cipher
             del(p)
 
-        return EncodeAES(self._cipher, prepare_data(text, AES.block_size))
+        return encode_AES(self._cipher, text)
 
     def decrypt(self, cipher_text):
         if not self._is_authenticated():
@@ -184,8 +189,7 @@ class CryptoEngine(object):  # pagma: no cover
             self._cipher = cipher
             del(p)
 
-        return DecodeAES(self._cipher, prepare_data(cipher_text,
-                                                    AES.block_size))
+        return decode_AES(self._cipher, cipher_text)
 
     def forget(self):
         """
@@ -239,6 +243,8 @@ class CryptoEngine(object):  # pagma: no cover
         """
         salt = base64.b64encode(os.urandom(32))
         passwd = self._getsecret("Please type in the master password")
+        if not isinstance(passwd, bytes):
+            passwd = passwd.encode()
         key = get_digest(passwd, salt)
         hpk = salt+'$6$'.encode('utf8')+binascii.hexlify(key)
         self._digest = key

+ 1 - 1
requirements.txt

@@ -1,2 +1,2 @@
-pycrypto>=2.6
+cryptography
 colorama>=0.2.4

+ 8 - 9
setup.py

@@ -6,7 +6,6 @@ script to install pwman3
 import datetime
 from distutils.core import Command
 from distutils.errors import DistutilsOptionError
-from distutils.command.build import build
 import argparse
 from setuptools import setup
 from setuptools import find_packages
@@ -318,10 +317,7 @@ class PyCryptoInstallCommand(install):
                    'to install pycrypto ...'))
 
 
-install_requires = ['colorama>=0.2.4']
-
-if sys.version_info.major < 3:
-    install_requires.append("future")
+install_requires = ['colorama>=0.2.4', 'cryptography']
 
 if sys.platform.startswith('win'):
     install_requires.append('pyreadline')
@@ -334,16 +330,19 @@ It allows one to store passwords in database locked by master password which
 is AES encrypted.
 Pwman3 supports MySQL, Postgresql and SQLite and even MongoDB"""
 
+packages = find_packages(exclude=['tests', 'pwman/ui/templates'])
+
 
 setup(name='pwman3',
-      version='0.8.1',
-      description = "a command line password manager with support for multiple databases.",
+      version='0.9.0',
+      description=("a command line password manager with support for multiple"
+                   " databases."),
       long_description=long_description,
       author='Oz Nahum Tiram',
       author_email='nahumoz@gmail.com',
       url='http://pwman3.github.io/pwman3/',
       license="GNU GPL",
-      packages=find_packages(exclude=['tests', 'pwman/ui/templates']),
+      packages=packages,
       include_package_data=True,
       zip_safe=False,
       install_requires=install_requires,
@@ -356,11 +355,11 @@ setup(name='pwman3',
                     ' v3 or later (GPLv3+)'),
                    'Operating System :: OS Independent',
                    'Programming Language :: Python',
-                   'Programming Language :: Python :: 2.7',
                    'Programming Language :: Python :: 3',
                    'Programming Language :: Python :: 3.2',
                    'Programming Language :: Python :: 3.3',
                    'Programming Language :: Python :: 3.4',
+                   'Programming Language :: Python :: 3.5',
                    ],
       test_suite='tests.test_pwman.suite',
       cmdclass={

+ 9 - 12
tests/test_base_ui.py

@@ -18,10 +18,7 @@
 # ============================================================================
 import os
 import unittest
-try:
-    from StringIO import StringIO
-except ImportError:
-    from io import StringIO
+from io import StringIO, BytesIO
 
 import sys
 from pwman.util.crypto_engine import CryptoEngine
@@ -72,13 +69,13 @@ class TestBaseUI(unittest.TestCase):
         sys.stdin = sys.__stdin__
 
     def test_1_do_new(self):
-        sys.stdin = StringIO(("alice\nsecret\nexample.com\nsome notes"
-                              "\nfoo bar baz"))
+        sys.stdin = BytesIO((b"alice\nsecret\nexample.com\nsome notes"
+                             b"\nfoo bar baz"))
         _node = self.tester.cli._do_new('')
 
         sys.stdin = sys.__stdin__
-        self.assertListEqual(['foo', 'bar', 'baz'], [t for t
-                                                     in _node.tags])
+        self.assertListEqual([b'foo', b'bar', b'baz'], [t for t
+                                                        in _node.tags])
         nodeid = self.tester.cli._db.listnodes()
         self.assertListEqual([1], nodeid)
         nodes = self.tester.cli._db.getnodes(nodeid)
@@ -92,9 +89,9 @@ class TestBaseUI(unittest.TestCase):
     def test_2_do_list(self):
         self.output = StringIO()
         sys.stdout = self.output
-        self.tester.cli.do_list(u'')
-        self.tester.cli.do_list(u'foo')
-        self.tester.cli.do_list(u'bar')
+        self.tester.cli.do_list('')
+        self.tester.cli.do_list('foo')
+        self.tester.cli.do_list('bar')
         sys.stdout = sys.__stdout__
         self.output.getvalue()
 
@@ -181,7 +178,7 @@ class TestBaseUI(unittest.TestCase):
     def test_10_do_info(self):
         self.output = StringIO()
         sys.stdout = self.output
-        self.tester.cli.do_info('')
+        self.tester.cli.do_info(b'')
         self.assertIn("test.pwman.db", sys.stdout.getvalue())
 
 if __name__ == '__main__':

+ 12 - 12
tests/test_crypto_engine.py

@@ -35,8 +35,8 @@ default_config = {'Global': {'umask': '0100', 'colors': 'yes',
                                                        "history")}
                   }
 
-give_key = lambda msg: "12345"
-give_wrong_key = lambda msg: "verywrongtkey"
+give_key = lambda msg: b"12345"
+give_wrong_key = lambda msg: b"verywrongtkey"
 
 salt = b'cUDHNMJdTRxiIDPXuT163UMvi4fd2pXz/bRg2Zm8ajE='
 digest = b'9eaec7dc1ee647338406739c54dbf9c4881c74702008eb978622811cfc46a07f'
@@ -45,13 +45,13 @@ digest = b'9eaec7dc1ee647338406739c54dbf9c4881c74702008eb978622811cfc46a07f'
 class DummyCallback(Callback):
 
     def getinput(self, question):
-        return u'12345'
+        return b'12345'
 
     def getsecret(self, question):
-        return u'12345'
+        return b'12345'
 
     def getnewsecret(self, question):
-        return u'12345'
+        return b'12345'
 
 
 class TestPassGenerator(unittest.TestCase):
@@ -104,8 +104,8 @@ class CryptoEngineTest(unittest.TestCase):
     def test5_e_authenticate(self):
         ce = CryptoEngine.get()
         ce._reader = give_key
-        self.assertFalse(ce.authenticate('verywrong'))
-        self.assertTrue(ce.authenticate('12345'))
+        self.assertFalse(ce.authenticate(b'verywrong'))
+        self.assertTrue(ce.authenticate(b'12345'))
         ce._timeout = -1
         self.assertTrue(ce._is_authenticated())
 
@@ -122,11 +122,11 @@ class CryptoEngineTest(unittest.TestCase):
         ce._reader = give_key
         if not ce._salt:
             ce._salt = salt
-        secret = ce.encrypt("topsecret")
+        secret = ce.encrypt(b"topsecret")
         decrypt = ce.decrypt(secret)
         self.assertEqual(decrypt.decode(), "topsecret")
         ce._cipher = None
-        secret = ce.encrypt("topsecret")
+        secret = ce.encrypt(b"topsecret")
         decrypt = ce.decrypt(secret)
         self.assertEqual(decrypt.decode(), "topsecret")
 
@@ -134,9 +134,9 @@ class CryptoEngineTest(unittest.TestCase):
         ce = CryptoEngine.get()
         ce._cipher = None
         ce._getsecret = give_wrong_key
-        self.assertRaises(CryptoException, ce.encrypt, "secret")
-        ce._getsecret = lambda x: u'12345'
-        secret = ce.encrypt(u"topsecret")
+        self.assertRaises(CryptoException, ce.encrypt, b"secret")
+        ce._getsecret = lambda x: b'12345'
+        secret = ce.encrypt(b"topsecret")
         decrypt = ce.decrypt(secret)
         self.assertEqual(decrypt.decode(), "topsecret")
 

+ 6 - 8
tests/test_importer.py

@@ -57,27 +57,27 @@ class TestImporter(unittest.TestCase):
         self.importer = CSVImporter(args,
                                     config, db)
 
-    def test_read_file(self):
+    def test_1_read_file(self):
         lines = self.importer._read_file()
         self.assertNotIn(["Username", "URL", "Password", "Notes", " Tags"],
                          lines)
 
-    def test_create_node(self):
+    def test_2_create_node(self):
         # create a node , should be encrypted, but not yet inserted to db
         n = "alice;wonderland.com;secert;scratch;foo,bar".split(";")
         node = self.importer._create_node(n)
         ce = CryptoEngine.get()
         self.assertEqual(ce.decrypt(node._username).decode(), u'alice')
-        self.assertEqual(['foo', 'bar'], [t for t in node.tags])
+        self.assertEqual([b'foo', b'bar'], [t for t in node.tags])
 
-    def test_insert_node(self):
+    def test_3_insert_node(self):
+        self.importer._open_db()
         n = "alice;wonderland.com;secert;scratch;foo,bar".split(";")
         node = self.importer._create_node(n)
-        self.importer._open_db()
         # do the actual insert of the node to the databse
         self.importer._insert_node(node)
 
-    def test_runner(self):
+    def test_4_runner(self):
         # test the whole procees:
         """
           open csv
@@ -99,8 +99,6 @@ class TestImporter(unittest.TestCase):
         importer = Importer((args, '', db))
         importer.importer.run(callback=DummyCallback)
 
-    def tearDown(self):
-        pass
 
 if __name__ == '__main__':
 

+ 5 - 8
tests/test_mongodb.py

@@ -84,7 +84,7 @@ class TestMongoDB(unittest.TestCase):
                   [u"bartag", u"footag"]]
 
         kwargs = {
-            "username":innode[0], "password": innode[1],
+            "username": innode[0], "password": innode[1],
             "url": innode[2], "notes": innode[3], "tags": innode[4]
         }
 
@@ -99,17 +99,14 @@ class TestMongoDB(unittest.TestCase):
     def test_6_list_nodes(self):
         ret = self.db.listnodes()
         self.assertEqual(ret, [1])
-        ce = CryptoEngine.get()
-        fltr = ce.encrypt("footag")
-        ret = self.db.listnodes(fltr)
-        self.assertEqual(ret, [1])
+        ret = self.db.listnodes(filter_=b"footag")
 
     def test_6a_list_tags(self):
         ret = self.db.listtags()
         ce = CryptoEngine.get()
-        ec_tags = map(ce.encrypt,[u'bartag', u'footag'])
-        for t in ec_tags:
-            self.assertIn(t, ret)
+        tags = list(map(ce.decrypt, ret))
+        for tag in tags:
+            self.assertIn(tag, [b'footag', b'bartag'])
 
     def test_6b_get_nodes(self):
         ret = self.db.getnodes([1])

+ 6 - 6
tests/test_mysql.py

@@ -53,12 +53,12 @@ class TestMySQLDatabase(unittest.TestCase):
 
     @classmethod
     def tearDownClass(self):
-        self.db._cur.execute("DROP TABLE LOOKUP")
-        self.db._cur.execute("DROP TABLE TAG")
-        self.db._cur.execute("DROP TABLE NODE")
-        self.db._cur.execute("DROP TABLE DBVERSION")
-        self.db._cur.execute("DROP TABLE CRYPTO")
-        self.db._con.commit()
+        for table in ['LOOKUP', 'TAG', 'NODE', 'DBVERSION', 'CRYPTO']:
+            try:
+                self.db._cur.execute("DROP TABLE {}".format(table))
+            except Exception:
+                pass
+            self.db._con.commit()
 
     def test_1_con(self):
         self.assertIsInstance(self.db._con, pymysql.connections.Connection)

+ 11 - 10
tests/test_nodes.py

@@ -25,9 +25,9 @@ from .test_crypto_engine import give_key, DummyCallback
 class TestNode(unittest.TestCase):
 
     def setUp(self):
-        self.node = Node(username=u'foo', password=u's3kr3t',
-                         url=u'example.com', notes=u'just a reminder to self',
-                         tags=[u'baz', u'baz'])
+        self.node = Node(username=b'foo', password=b's3kr3t',
+                         url=b'example.com', notes=b'just a reminder to self',
+                         tags=[b'baz', b'baz'])
 
     def test_do_encdict(self):
         ce = CryptoEngine.get()
@@ -39,17 +39,18 @@ class TestNode(unittest.TestCase):
                 self.assertEqual(ce.decrypt(v).decode(), getattr(self.node, k))
 
     def test_setters(self):
-        new_node = {'username': 'baz', 'password': 'n3ws3k43t',
-                    'notes': 'i have changed the password',
-                    'url': 'newexample.com', 'tags': ['tag1', 'tag2']}
+        new_node = {'username': b'baz', 'password': b'n3ws3k43t',
+                    'notes': b'i have changed the password',
+                    'url': b'newexample.com', 'tags': [b'tag1', b'tag2']}
 
         for k in new_node:
             setattr(self.node, k, new_node[k])
 
-        self.assertEqual(getattr(self.node, 'username'), new_node['username'])
-        self.assertEqual(getattr(self.node, 'password'), new_node['password'])
-        self.assertEqual(getattr(self.node, 'url'), new_node['url'])
-        self.assertEqual(getattr(self.node, 'notes'), new_node['notes'])
+        for attribute in ['username', 'password', 'url', 'notes']:
+            self.assertEqual(bytearray(getattr(self.node, attribute), 'utf-8'), new_node[attribute])
+
+        self.assertEqual(bytearray(getattr(self.node, 'username'), 'utf-8'), new_node['username'])
+        self.assertEqual(bytearray(getattr(self.node, 'password'), 'utf-8'), new_node['password'])
         self.assertEqual(getattr(self.node, 'tags'), new_node['tags'])
 
 

+ 14 - 14
tests/test_postgresql.py

@@ -67,32 +67,32 @@ class TestPostGresql(unittest.TestCase):
     def test_3_load_key(self):
         self.db.savekey('SECRET$6$KEY')
         secretkey = self.db.loadkey()
-        self.assertEqual(secretkey, 'SECRET$6$KEY')
+        self.assertEqual(secretkey, b'SECRET$6$KEY')
 
     def test_4_save_crypto(self):
-        self.db.save_crypto_info("TOP", "SECRET")
+        self.db.save_crypto_info(b"TOP", b"SECRET")
         secretkey = self.db.loadkey()
-        self.assertEqual(secretkey, 'TOP$6$SECRET')
+        self.assertEqual(secretkey, b'TOP$6$SECRET')
         row = self.db.fetch_crypto_info()
-        self.assertEqual(row, ('TOP', 'SECRET'))
+        self.assertEqual(list(map(bytearray, row)),
+                         [bytearray(b'TOP'), bytearray(b'SECRET')])
 
     def test_5_add_node(self):
-        innode = ["TBONE", "S3K43T", "example.org", "some note",
-                  ["footag", "bartag"]]
+        innode = [b"TBONE", b"S3K43T", b"example.org", b"some note",
+                  [b"footag", b"bartag"]]
         self.db.add_node(innode)
-
         outnode = self.db.getnodes([1])[0]
         self.assertEqual(innode[:-1] + [t for t in innode[-1]], outnode[1:])
 
     def test_6_list_nodes(self):
-        ret = self.db.listnodes()
-        self.assertEqual(ret, [1])
-        ret = self.db.listnodes("footag")
-        self.assertEqual(ret, [1])
+        ret1 = self.db.listnodes()
+        self.assertEqual(ret1, [1])
+        ret2 = self.db.listnodes(b"footag")
+        self.assertEqual(ret2, [1])
 
     def test_6a_list_tags(self):
         ret = self.db.listtags()
-        self.assertListEqual(ret, ['footag', 'bartag'])
+        self.assertListEqual(ret, [b'footag', b'bartag'])
 
     def test_6b_get_nodes(self):
         ret = self.db.getnodes([1])
@@ -100,8 +100,8 @@ class TestPostGresql(unittest.TestCase):
         self.assertListEqual(ret, retb)
 
     def test_7_get_or_create_tag(self):
-        s = self.db._get_or_create_tag("SECRET")
-        s1 = self.db._get_or_create_tag("SECRET")
+        s = self.db._get_or_create_tag(b"SECRET")
+        s1 = self.db._get_or_create_tag(b"SECRET")
 
         self.assertEqual(s, s1)
 

+ 24 - 26
tests/test_sqlite.py

@@ -58,30 +58,28 @@ class TestSQLite(unittest.TestCase):
 
     def test_3_add_node(self):
         node = Node(clear_text=True,
-                    **{'username': u"alice", 'password': u"secret",
-                       'url': u"wonderland.com",
-                       'notes': u"a really great place",
-                       'tags': [u'foo', u'bar']})
+                    **{'username': "alice", 'password': "secret",
+                       'url': "wonderland.com",
+                       'notes': "a really great place",
+                       'tags': ['foo', 'bar']})
         self.db.add_node(node)
         rv = self.db._cur.execute("select * from node")
-        # clearly this fails, while alice is not found in clear text in the
-        # database!
         ce = CryptoEngine.get()
         res = rv.fetchone()
-        self.assertIn(ce.encrypt(u'alice'), res[1])
+        self.assertEqual(ce.decrypt(res[1]), b"alice")
 
     def test_4_test_tags(self):
         node = Node(clear_text=True,
                     **{'username': u"alice", 'password': u"secret",
                        'url': u"wonderland.com",
                        'notes': u"a really great place",
-                       'tags': [u'foo', u'bar']})
+                       'tags': ['foo', 'bar']})
         ce = CryptoEngine.get()
         self.db._get_or_create_tag(node._tags[0])
         self.assertEqual(1, self.db._get_or_create_tag(node._tags[0]))
-        rv = self.db._get_or_create_tag(ce.encrypt('baz'))
-        self.db._con.commit()
+        rv = self.db._get_or_create_tag(ce.encrypt(b'baz'))
         self.assertEqual(3, rv)
+        self.db._con.commit()
 
     def test_5_test_lookup(self):
         self.db._cur.execute('SELECT nodeid, tagid FROM LOOKUP')
@@ -104,10 +102,10 @@ class TestSQLite(unittest.TestCase):
         # test_3_add_node
         # test_6_listnodes
 
-        tag = ce.encrypt(u'bar')
+        tag = ce.encrypt(b'bar')
         rv = self.db.listnodes(tag)
         self.assertEqual(len(rv), 2)
-        tag = ce.encrypt(u'baz')
+        tag = ce.encrypt(b'baz')
         # the tag 'baz' is found in a node created in
         # test_6_listnodes
         rv = self.db.listnodes(tag)
@@ -119,36 +117,36 @@ class TestSQLite(unittest.TestCase):
 
     def test_9_editnode(self):
         # delibertly insert clear text into the database
+        ce = CryptoEngine.get()
+        tags = [ce.encrypt("foo"), ce.encrypt("auto")]
         node = {'user': 'transparent', 'password': 'notsecret',
-                'tags': ['foo', 'bank']}
+                'tags': tags}
         self.db.editnode('2', **node)
         self.db._cur.execute('SELECT USER, PASSWORD FROM NODE WHERE ID=2')
         rv = self.db._cur.fetchone()
-        self.assertEqual(rv, (u'transparent', u'notsecret'))
+        self.assertEqual(rv, ('transparent', 'notsecret'))
         node = {'user': 'modify', 'password': 'notsecret',
-                'tags': ['foo', 'auto']}
+                'tags': tags}
         # now the tags bank and baz are orphan ...
         # what happens? it should be completely removed.
         # To spare IO we only delete orphand tags when
         # db.close is called.
         self.db.editnode('2', **node)
 
-    def test_9_test_orphans(self):
+    def test_9_test_no_orphans(self):
         self.db._clean_orphans()
+        self.db._con.commit()
         ce = CryptoEngine.get()
-        baz_encrypted = ce.encrypt(u'baz').decode()
-
-        self.db._cur.execute('SELECT DATA FROM TAG')
-        rv = self.db._cur.fetchall()
-        for data in rv:
-            if isinstance(data[0], str):
-                self.assertNotIn(u'bank', data[0])
-            else:
-                self.assertNotIn(baz_encrypted, data[0].decode())
+        tags = None
+        while not tags:
+            tags = self.db._cur.execute('SELECT * FROM tag').fetchall()
+        tags_clear = [ce.decrypt(tag[1]) for tag in tags]
+        self.assertNotIn(b"baz", tags_clear)
 
     def test_a10_test_listtags(self):
+        """there should be only 3 tags left"""
         tags = self.db.listtags()
-        self.assertEqual(4, len(list(tags)))
+        self.assertEqual(3, len(list(tags)))
 
     def test_a11_test_rmnodes(self):
         for n in [1, 2]:

+ 8 - 8
tests/test_tools.py

@@ -31,38 +31,38 @@ PwmanCliNew, OSX = get_ui_platform(sys.platform)
 class DummyCallback(Callback):
 
     def getinput(self, question):
-        return u'12345'
+        return b'12345'
 
     def getsecret(self, question):
-        return u'12345'
+        return b'12345'
 
 
 class DummyCallback2(Callback):
 
     def getinput(self, question):
-        return u'newsecret'
+        return b'newsecret'
 
     def getsecret(self, question):
-        return u'wrong'
+        return b'wrong'
 
 
 class DummyCallback3(Callback):
 
     def getinput(self, question):
-        return u'newsecret'
+        return b'newsecret'
 
     def getsecret(self, question):
-        ans = '12345'
+        ans = b'12345'
         return ans
 
 
 class DummyCallback4(Callback):
 
     def getinput(self, question):
-        return u'newsecret'
+        return b'newsecret'
 
     def getsecret(self, question):
-        return u'newsecret'
+        return b'newsecret'
 
 
 config.default_config['Database'] = {'type': 'sqlite',

+ 3 - 3
tox.ini

@@ -4,9 +4,9 @@
 # and then run "tox" from this directory.
 
 [tox]
-envlist = py27,py34
+envlist = py34,py35
 
-[testenv:py27]
+[testenv:py34]
 commands = coverage erase
        {envbindir}/python setup.py develop
        coverage run -p setup.py test
@@ -19,7 +19,7 @@ deps = -rrequirements.txt
         pexpect
         coverage
 
-[testenv:py34]
+[testenv:py35]
 commands = coverage3 erase
        {envbindir}/python setup.py develop
        coverage3 run -p setup.py test