diff --git a/CHANGELOG b/CHANGELOG index b77a6f2..0df2409 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +- 0.0.6 + * Add cache encryption, with AES-CTR-128 - 0.0.5 * Replace logs with colorized progress output on default loglevel * Do not repeat already failed metadata lookups diff --git a/clicada/cli/arg.py b/clicada/cli/arg.py index 4c7564a..2ea2cb2 100644 --- a/clicada/cli/arg.py +++ b/clicada/cli/arg.py @@ -1,7 +1,7 @@ # import notifier from clicada.cli.notify import NotifyWriter notifier = NotifyWriter() -notifier.notify('loading script') +#notifier.notify('loading script') # standard imports import os @@ -22,6 +22,7 @@ from clicada.cli.http import ( HTTPSession, PGPClientSession, ) +from clicada.crypt.aes import AESCTREncrypt logg = logging.getLogger() @@ -150,6 +151,7 @@ class CmdCtrl: auth_db_path = self.get('AUTH_DB_PATH', default_auth_db_path) self.__auth = PGPAuthCrypt(auth_db_path, self.get('AUTH_KEY'), self.get('AUTH_KEYRING_PATH')) self.__auth.get_secret(self.get('AUTH_PASSPHRASE')) + self.encrypter = AESCTREncrypt(auth_db_path, self.__auth.secret) def get(self, k, default=None): diff --git a/clicada/cli/auth.py b/clicada/cli/auth.py index 5954eb9..d5dcb78 100644 --- a/clicada/cli/auth.py +++ b/clicada/cli/auth.py @@ -26,6 +26,7 @@ class PGPAuthCrypt: raise AuthError('invalid key {}'.format(auth_key)) self.auth_key = auth_key self.gpg = gnupg.GPG(gnupghome=pgp_dir) + self.secret = None def get_secret(self, passphrase=''): @@ -49,10 +50,11 @@ class PGPAuthCrypt: f.write(secret.data) f.close() f = open(p, 'rb') - self.secret = self.gpg.decrypt_file(f, passphrase=passphrase) - if not self.secret.ok: + secret = self.gpg.decrypt_file(f, passphrase=passphrase) + if not secret.ok: raise AuthError('could not decrypt encryption secret. wrong password?') f.close() + self.secret = secret.data self.__passphrase = passphrase diff --git a/clicada/cli/http.py b/clicada/cli/http.py index 0ae7dda..f49ceee 100644 --- a/clicada/cli/http.py +++ b/clicada/cli/http.py @@ -3,6 +3,7 @@ import hashlib import urllib.parse import os import logging +from socket import getservbyname # external imports from usumbufu.client.base import ( @@ -48,7 +49,15 @@ class HTTPSession: def __init__(self, url, auth=None, origin=None): self.base_url = url url_parts = urllib.parse.urlsplit(self.base_url) - url_parts_origin = (url_parts[0], url_parts[1], '', '', '',) + url_parts_origin_host = url_parts[1].split(":") + host = url_parts_origin_host[0] + try: + host = host + ':' + url_parts_origin_host[1] + except IndexError: + host = host + ':' + str(getservbyname(url_parts[0])) + logg.info('changed origin with missing port number from {} to {}'.format(url_parts[1], host)) + url_parts_origin = (url_parts[0], host, '', '', '',) + self.origin = origin if self.origin == None: self.origin = urllib.parse.urlunsplit(url_parts_origin) diff --git a/clicada/cli/notify.py b/clicada/cli/notify.py index 6b8d8ef..8ee25b9 100644 --- a/clicada/cli/notify.py +++ b/clicada/cli/notify.py @@ -1,12 +1,13 @@ # standard imports import os import sys +import shutil class NotifyWriter: def __init__(self, writer=sys.stdout): - (c, r) = os.get_terminal_size() + (c, r) = shutil.get_terminal_size() self.cols = c self.fmt = "\r{:" + "<{}".format(c) + "}" self.w = writer diff --git a/clicada/cli/user.py b/clicada/cli/user.py index c4a4794..92a0aa0 100644 --- a/clicada/cli/user.py +++ b/clicada/cli/user.py @@ -56,7 +56,7 @@ def execute(ctrl): store_path = '.clicada' user_phone_file_label = 'phone' - user_phone_store = FileUserStore(ctrl.opener('meta'), ctrl.chain(), user_phone_file_label, store_path, int(ctrl.get('FILESTORE_TTL'))) + user_phone_store = FileUserStore(ctrl.opener('meta'), ctrl.chain(), user_phone_file_label, store_path, int(ctrl.get('FILESTORE_TTL')), encrypter=ctrl.encrypter) ctrl.notify('resolving identifier {} to wallet address'.format(ctrl.get('_IDENTIFIER'))) user_address = user_phone_store.by_phone(ctrl.get('_IDENTIFIER'), update=ctrl.get('_FORCE')) @@ -78,7 +78,7 @@ def execute(ctrl): token_store = FileTokenStore(ctrl.chain(), ctrl.conn(), 'token', store_path) user_address_file_label = 'address' - user_address_store = FileUserStore(ctrl.opener('meta'), ctrl.chain(), user_address_file_label, store_path, int(ctrl.get('FILESTORE_TTL'))) + user_address_store = FileUserStore(ctrl.opener('meta'), ctrl.chain(), user_address_file_label, store_path, int(ctrl.get('FILESTORE_TTL')), encrypter=ctrl.encrypter) ctrl.notify('resolving metadata for address {}'.format(user_address_normal)) try: diff --git a/clicada/crypt/aes.py b/clicada/crypt/aes.py new file mode 100644 index 0000000..8d8afb4 --- /dev/null +++ b/clicada/crypt/aes.py @@ -0,0 +1,42 @@ +# standard imports +import os +import logging +import hashlib + +from Crypto.Cipher import AES +from Crypto.Util import Counter + +from .base import Encrypter + +logg = logging.getLogger(__name__) + + +class AESCTREncrypt(Encrypter): + + aes_block_size = 1 << 7 + counter_bytes = int(128 / 8) + + def __init__(self, db_dir, secret): + self.secret = secret + + + def key_to_iv(self, k): + h = hashlib.sha256() + h.update(k.encode('utf-8')) + h.update(self.secret) + z = h.digest() + return int.from_bytes(z[:self.counter_bytes], 'big') + + + def encrypt(self, k, v): + iv = self.key_to_iv(k) + ctr = Counter.new(self.aes_block_size, initial_value=iv) + cipher = AES.new(self.secret, AES.MODE_CTR, counter=ctr) + return cipher.encrypt(v) + + + def decrypt(self, k, v): + iv = self.key_to_iv(k) + ctr = Counter.new(self.aes_block_size, initial_value=iv) + cipher = AES.new(self.secret, AES.MODE_CTR, counter=ctr) + return cipher.decrypt(v) diff --git a/clicada/crypt/base.py b/clicada/crypt/base.py new file mode 100644 index 0000000..19bc150 --- /dev/null +++ b/clicada/crypt/base.py @@ -0,0 +1,8 @@ +class Encrypter: + + def encrypt(self, v): + raise NotImplementedError() + + + def decrypt(self, v): + raise NotImplementedError() diff --git a/clicada/user/file.py b/clicada/user/file.py index a84a4f9..02bf037 100644 --- a/clicada/user/file.py +++ b/clicada/user/file.py @@ -65,7 +65,7 @@ class Account(Person): class FileUserStore: - def __init__(self, metadata_opener, chain_spec, label, store_base_path, ttl): + def __init__(self, metadata_opener, chain_spec, label, store_base_path, ttl, encrypter=None): invalidate_before = datetime.datetime.now() - datetime.timedelta(seconds=ttl) self.invalidate_before = int(invalidate_before.timestamp()) self.have_xattr = False @@ -82,6 +82,7 @@ class FileUserStore: self.__validate_dir() self.metadata_opener = metadata_opener self.failed_entities = {} + self.encrypter = encrypter def __validate_dir(self): @@ -108,8 +109,14 @@ class FileUserStore: if have_file and not its_time and not force: raise FileExistsError('user resolution already exists for {}'.format(k)) - f = open(p, 'w') - f.write(v) + ve = v + f = None + if self.encrypter != None: + ve = self.encrypter.encrypt(k, ve.encode('utf-8')) + f = open(p, 'wb') + else: + f = open(p, 'w') + f.write(ve) f.close() logg.info('added user store {} record {} -> {}'.format(self.label, k, v)) @@ -174,12 +181,21 @@ class FileUserStore: self.__unstick(p) self.check_expiry(p) - f = open(p, 'r') - r = f.read() + f = None + if self.encrypter != None: + f = open(p, 'rb') + else: + f = open(p, 'r') + v = f.read() f.close() + if self.encrypter != None: + v = self.encrypter.decrypt(k, v) + logg.debug('>>>>>>>>>>>>< v decoded {}'.format(v)) + v = v.decode('utf-8') + logg.debug('retrieved {} from {}'.format(k, p)) - return r.strip() + return v.strip() def by_phone(self, phone, update=False): diff --git a/requirements.txt b/requirements.txt index f210ea9..2003eba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,10 @@ -usumbufu~=0.3.4 -confini~=0.5.1 +usumbufu~=0.3.5 +confini~=0.5.3 cic-eth-registry~=0.6.1 cic-types~=0.2.1a8 phonenumbers==8.12.12 eth-erc20~=0.1.2 hexathon~=0.1.0 +pycryptodome~=3.10.1 +chainlib-eth~=0.0.21 +chainlib~=0.0.17 diff --git a/setup.cfg b/setup.cfg index ecf7664..17328ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = clicada -version = 0.0.5a1 +version = 0.0.6a2 description = CLI CRM tool for the cic-stack custodial wallet system author = Louis Holbrook author_email = dev@holbrook.no @@ -34,3 +34,4 @@ packages = clicada.cli clicada.tx clicada.user + clicada.crypt