+++ /dev/null
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2018 Michael Rasmussen <mir@datanom.net>
-
-# This file is part of SecureMail.
-
-# SecureMail is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# SecureMail is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with SecureMail. If not, see <https://www.gnu.org/licenses/>.
-
-###### REQUIREMENTS #######
-# python3-nacl #
-# python3-mysqldb #
-# python3-psycopg2 #
-# python3-apsw #
-# mysql, postgresql or sqlite #
-###############################
-
-#DBTYPE = "mysql"
-#DBHOST = "localhost" # default value
-#DBPORT = 3306 # default value
-#DBTYPE = "postgresql"
-#DBHOST = "localhost" # default value
-#DBPORT = 5432 # default value
-DBTYPE = "sqlite"
-#DBUID = "backend" # default value
-#DBPWD = "clV77B2ZJQxr" # default value
-DBNAME = "securemail" # if DBTYPE is sqlite: ./DBNAME + .db
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2018 Michael Rasmussen <mir@datanom.net>
-
-# This file is part of SecureMail.
-
-# SecureMail is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# SecureMail is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with SecureMail. If not, see <https://www.gnu.org/licenses/>.
-
-from nacl import __version__ as NACL_VERSION
-from nacl.secret import SecretBox
-from nacl.public import PrivateKey, Box
-from nacl.utils import random, EncryptedMessage
-from nacl.encoding import HexEncoder
-import nacl.hash
-
-class Cryptonize:
- """
- Encrypt and decrypt objects
- """
-
- def symmetric_encrypt(self, key, plain):
- skey = self.sanitize_key(key)
- box = SecretBox(skey)
- if NACL_VERSION < "1.1.0":
- nonce = random(SecretBox.NONCE_SIZE)
- cipher = box.encrypt(plain, nonce)
- else:
- cipher = box.encrypt(plain)
- box = skey = None
-
- return cipher
-
- def symmetric_decrypt(self, key, cipher):
- skey = self.sanitize_key(key)
- box = SecretBox(skey)
- plain = box.decrypt(cipher)
- box = skey = None
-
- return plain
-
- def asymmetric_encrypt(self, privkey, pubkey, plain):
- if not isinstance(plain, bytes):
- plain = plain.encode('utf-8')
- box = Box(privkey, pubkey)
- if NACL_VERSION < "1.1.0":
- nonce = random(Box.NONCE_SIZE)
- cipher = box.encrypt(plain, nonce)
- else:
- cipher = box.encrypt(plain)
- box = None
-
- return cipher
-
- def asymmetric_decrypt(self, privkey, pubkey, cipher):
- if not isinstance(cipher, bytes):
- cipher = cipher.encode('utf-8')
- box = Box(privkey, pubkey)
- plain = box.decrypt(cipher)
- box = None
-
- return plain
-
- def get_random_key(self):
- return random(SecretBox.KEY_SIZE)
-
- def sanitize_key(self, key):
- if not isinstance(key, bytes):
- key = key.encode('utf-8')
- size = len(key)
- if size < SecretBox.KEY_SIZE:
- """ We must pad """
- newkey = key + bytes(SecretBox.KEY_SIZE - size)
- elif size > SecretBox.KEY_SIZE:
- newkey = key[:SecretBox.KEY_SIZE]
- else:
- newkey = key
-
-
- return newkey
-
- def get_key_pair(self):
- privkey = PrivateKey.generate()
- pubkey = privkey.public_key
-
- return (privkey, pubkey)
-
- def generate_hash(self, key):
- if not isinstance(key, bytes):
- key = key.encode('utf-8')
- HASHER = nacl.hash.sha512
- digest = HASHER(key, encoder=HexEncoder)
-
- return digest.decode()
-
- def create_EncryptedMessage(self, payload):
- nonce = payload[:SecretBox.NONCE_SIZE]
- ciphertext = payload[SecretBox.NONCE_SIZE:]
-
- return EncryptedMessage._from_parts(
- nonce, ciphertext, nonce + ciphertext)
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2018 Michael Rasmussen <mir@datanom.net>
-
-# This file is part of SecureMail.
-
-# SecureMail is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# SecureMail is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with SecureMail. If not, see <https://www.gnu.org/licenses/>.
-
-# sqlite
-sqlite_sql = """create table account (
-id int auto_increment,
-token char(128) unique not null,
-cipher text not null,
-primary key (id))"""
-
-# mysql
-mysql_sql = """create table account (
-id int auto_increment,
-token char(128) unique not null,
-cipher text not null,
-primary key (id))"""
-
-# postgresql
-postgresql_sql = """create table account (
-id serial,
-token char(128) unique not null,
-cipher bytea not null,
-primary key (id))"""
-
-import base64
-from config import DBTYPE, DBNAME
-try:
- from config import DBUID
-except ImportError:
- DBUID = 'backend'
-try:
- from config import DBPWD
-except ImportError:
- DBPWD = 'clV77B2ZJQxr'
-try:
- from config import DBHOST
-except ImportError:
- DBHOST = 'localhost'
-try:
- from config import DBPORT
-except ImportError:
- if DBTYPE == 'mysql':
- DBPORT = 3306
- elif DBTYPE == 'postgresql':
- DBPORT = 5432
-from cryptonize import Cryptonize
-
-class Singleton:
- def __init__(self, klass):
- self.klass = klass
- self.instance = None
-
- def __call__(self, *args, **kwargs):
- if self.instance == None:
- self.instance = self.klass(*args, **kwargs)
- return self.instance
-
-@Singleton
-class DB:
- conn = None
-
- def get_connection(self):
- if self.conn is None:
- if DBTYPE == 'mysql':
- import MySQLdb
- self.conn = MySQLdb.connect(host=DBHOST, port=DBPORT, user=DBUID, passwd=DBPWD, db=DBNAME)
- elif DBTYPE == 'postgresql':
- import psycopg2
- self.conn = psycopg2.connect(host=DBHOST, port=DBPORT, user=DBUID, password=DBPWD, dbname=DBNAME)
- elif DBTYPE == 'sqlite':
- import apsw
- self.conn = apsw.Connection('./{0}.db'.format(DBNAME))
- else:
- raise ValueError('{0}: Unsupported database'.format(DBTYPE))
- return self.conn
-
- def __del__(self):
- if self.conn is not None:
- self.conn.close()
-
-class DBInterface:
- @staticmethod
- def load_user(key):
- conn = DB().get_connection()
- cursor = conn.cursor()
- cursor.execute("select a.cipher from account a where token = '{0}'".format(key))
- row = cursor.fetchone()
- if row is None:
- obj = None
- else:
- c = Cryptonize()
- msg = base64.b64decode(row[0])
- obj = c.create_EncryptedMessage(msg)
- cursor.close()
-
- return obj
-
- @staticmethod
- def store_user(key, cipher):
- if DBTYPE == 'mysql':
- from MySQLdb import Error as DBError
- elif DBTYPE == 'postgresql':
- from psycopg2 import Error as DBError
- elif DBTYPE == 'sqlite':
- from apsw import Error as DBError
- conn = DB().get_connection()
- cursor = conn.cursor()
- raw = base64.b64encode(cipher)
- try:
- if DBTYPE != 'sqlite':
- cursor.execute("insert into account(token, cipher) values(%s, %s)", (key, raw))
- conn.commit()
- else:
- cursor.execute('begin')
- cursor.execute("insert into account(token, cipher) values(?, ?)", (key, raw))
- cursor.execute('commit')
- except DBError as e:
- print (e)
- if DBTYPE != 'sqlite':
- conn.rollback()
- else:
- cursor.execute('rollback')
- raise e
- finally:
- cursor.close()
-
- @staticmethod
- def create_database():
- if DBTYPE == 'mysql':
- from MySQLdb import Error as DBError
- elif DBTYPE == 'postgresql':
- from psycopg2 import Error as DBError
- elif DBTYPE == 'sqlite':
- from apsw import Error as DBError
- conn = DB().get_connection()
- cursor = conn.cursor()
- try:
- if DBTYPE != 'sqlite':
- if DBTYPE == 'mysql':
- sql = mysql_sql
- elif DBTYPE == 'postgresql':
- sql = postgresql_sql
- cursor.execute(sql)
- conn.commit()
- else:
- cursor.execute('begin')
- cursor.execute(sqlite_sql)
- cursor.execute('commit')
- except DBError as e:
- if DBTYPE != 'sqlite':
- conn.rollback()
- else:
- cursor.execute('rollback')
- raise e
- finally:
- cursor.close()
-
-def main():
- from optparse import OptionParser
-
- usage = "usage: %prog [options] arg"
- parser = OptionParser(usage)
- parser.add_option("-c", "--create", action="store_true", dest="create",
- help="Create tables in database using config.py", default=False)
- parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
- help="Run in verbose mode", default=False)
- (options, args) = parser.parse_args()
-
- if options.create:
- try:
- if options.verbose:
- print("Creating empty database")
- print("Database Engine: {0}".format(DBTYPE))
- if DBTYPE != 'sqlite':
- print("Database Host: {0}".format(DBHOST))
- print("Database Port: {0}".format(DBPORT))
- else:
- print("Database File: ./{0}.db".format(DBNAME))
- DBInterface.create_database()
- print("Database created")
- except Exception as e:
- print("Creating database failed!")
- print(e)
-
-if __name__ == '__main__':
- main()
<Email>mir@datanom.net</Email>
<Eol index="0"/>
<Sources>
- <Source>__init__.py</Source>
- <Source>config.py</Source>
- <Source>cryptonize.py</Source>
- <Source>db.py</Source>
- <Source>user.py</Source>
+ <Source>app/backend/__init__.py</Source>
+ <Source>app/backend/config.py</Source>
+ <Source>app/backend/cryptonize.py</Source>
+ <Source>app/backend/db.py</Source>
+ <Source>app/backend/user.py</Source>
</Sources>
<Others>
<Other>.gitignore</Other>
<Other>securemail.e4p</Other>
</Others>
- <MainScript>user.py</MainScript>
+ <MainScript>app/backend/user.py</MainScript>
<Vcs>
<VcsType>Git</VcsType>
<VcsOptions>
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2018 Michael Rasmussen <mir@datanom.net>
-
-# This file is part of SecureMail.
-
-# SecureMail is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# SecureMail is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with SecureMail. If not, see <https://www.gnu.org/licenses/>.
-
-import pickle
-from db import DBInterface as DBI
-from cryptonize import Cryptonize
-from nacl.public import PublicKey
-
-class NoSuchUser(Exception):
- pass
-
-class User:
- """
- Class implementing the backend users
- """
- def __init__(self, key=None):
- if key is not None:
- self.load(key)
- else:
- self.pubkeys = {}
-
- def store(self, key):
- crypto = Cryptonize()
- cipher = crypto.symmetric_encrypt(key, pickle.dumps(self))
- DBI.store_user(crypto.generate_hash(key), cipher)
-
- def load(self, key):
- crypto = Cryptonize()
- cipher = DBI.load_user(crypto.generate_hash(key))
- if cipher is None:
- raise NoSuchUser('{0}: User not found'.format(key))
- plain = crypto.symmetric_decrypt(key, cipher)
- try:
- obj = pickle.loads(plain)
- self.__dict__.update(obj.__dict__)
- except pickle.UnpicklingError as e:
- raise e
-
- def add_pubkey(self, email, key):
- if email not in self.pubkeys:
- self.pubkeys[email] = key.encode()
- else:
- raise KeyError('{0}: Exists'.format(email))
-
- def update_pubkey(self, email, key):
- self.pubkeys[email] = key.encode()
-
- def delete_pubkey(self, email):
- if email in self.pubkeys:
- del self.pubkeys[email]
-
- def get_pubkey(self, email):
- if email in self.pubkeys:
- key = self.pubkeys[email]
- key = PublicKey(key)
- else:
- key = None
-
- return key
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def email(self):
- return self._email
-
- @email.setter
- def email(self, email):
- self._email = email
-
- @property
- def pubkeys(self):
- return self._pubkeys
-
- @pubkeys.setter
- def pubkeys(self, pubkeys):
- if type(pubkeys) is not type({}):
- raise ValueError('Not dictionary')
- self._pubkeys = pubkeys
-
-if __name__ == '__main__':
- try:
- u = User('test')
- for attr, value in u.__dict__.items():
- print ('{0}: {1}'.format(attr, value))
- print ('{0} - {1} - {2}'.format(u.name, u.email, u.pubkeys))
- key = ''
- for i in range(40):
- key += '{0}'.format(i)
- u = User()
- u.name = 'testname1'
- u.email = 'testname1@securemail.icu'
- u.pubkeys = {'test': 'some test', 'test1': 'some test 1'}
- try:
- u.store(key)
- except:
- u = User(key)
- for attr, value in u.__dict__.items():
- print ('{0}: {1}'.format(attr, value))
- print ('{0} - {1} - {2}'.format(u.name, u.email, u.pubkeys))
- c = Cryptonize()
- keypair1 = c.get_key_pair()
- keypair2 = c.get_key_pair()
- try:
- u.add_pubkey('test', keypair2[1])
- except KeyError:
- u.update_pubkey('test', keypair2[1])
- message = "Kill all humans æøåÅØÆ"
- print ("Message to encrypt: {0}".format(message))
- encrypted = c.asymmetric_encrypt(keypair1[0], u.get_pubkey('test'), message)
- print ("Message encrypted: {0}".format(encrypted))
- plaintext = c.asymmetric_decrypt(keypair2[0], keypair1[1], encrypted)
- print("Message decrypted: {0}".format(plaintext.decode()))
- except NoSuchUser:
- u = User()
- u.name = 'testname'
- u.email = 'testname@securemail.icu'
- u.store('test')
- except Exception as e:
- print (e)