diff --git a/api/persona.py b/api/persona.py deleted file mode 100644 index 7471c40..0000000 --- a/api/persona.py +++ /dev/null @@ -1,739 +0,0 @@ -import os -import asyncio -from pythemis.skeygen import KEY_PAIR_TYPE, GenerateKeyPair -from pythemis.smessage import SMessage, ssign, sverify -from pythemis.exception import ThemisError -from pythemis.scell import SCellSeal - -from base64 import b64decode,b64encode -# from kademlia.network import Server -import os,time,sys,logging -from pathlib import Path -import requests - -sys.path.append('../p2p') -BSEP=b'||||||||||' -BSEP2=b'@@@@@@@@@@' -BSEP3=b'##########' -NODE_SLEEP_FOR=1 - -P2P_PREFIX=b'/persona/' -P2P_PREFIX_POST=b'/msg/' -P2P_PREFIX_INBOX=b'/inbox/' -P2P_PREFIX_OUTBOX=b'/outbox/' - -WORLD_PUB_KEY = b'VUVDMgAAAC1z53KeApQY4RICK5k0nXnnS+K17veIFMPlFKo7mqnRhTZDhAmG' -WORLD_PRIV_KEY = b'UkVDMgAAAC26HXeGACxZUoKYKlZ7sDmVoLwffNj3CrdqoPrE94+2ysfhufmP' - -KOMRADE_PUB_KEY = b'VUVDMgAAAC09uo+wAgu/V9xyvMkMDbOQEk1ssOrFADaiyTzfwVjE6o8FHoil' -KOMRADE_PRIV_KEY = b'UkVDMgAAAC33fFiaAIpmQewjkYndzMcMkj1mLy/lE4RXJQzIlUN94tyC5g29' - -DEBUG = True -UPLOAD_DIR = 'uploads/' -ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} - -PORT_LISTEN = 5639 -NODE_SLEEP_FOR=1 -NODES_PRIME = [("128.232.229.63",8467)] -KEYSERVER_ADDR = 'komrade.app' #'128.232.229.63' -KEYSERVER_PORT = 5566 - - -KEY_PATH = os.path.join(os.path.expanduser('~'),'.komrade') -KEY_PATH_PUB = os.path.join(KEY_PATH,'.locs') -KEY_PATH_PRIV = os.path.join(KEY_PATH,'.keys') - -for x in [KEY_PATH,KEY_PATH_PUB,KEY_PATH_PRIV]: - if not os.path.exists(x): os.makedirs(x) - -WORLD_PRIV_KEY_FN = os.path.join(KEY_PATH_PRIV,'.world.key') -WORLD_PUB_KEY_FN = os.path.join(KEY_PATH_PUB,'.world.loc') -KOMRADE_PRIV_KEY_FN = os.path.join(KEY_PATH_PRIV,'.komrade.key') -KOMRADE_PUB_KEY_FN = os.path.join(KEY_PATH_PUB,'.komrade.loc') - - -def check_world_keys(): - if not os.path.exists(WORLD_PRIV_KEY_FN): - with open(WORLD_PRIV_KEY_FN,'wb') as of: - of.write(WORLD_PRIV_KEY) - - if not os.path.exists(WORLD_PUB_KEY_FN): - with open(WORLD_PUB_KEY_FN,'wb') as of: - of.write(WORLD_PUB_KEY) - - if not os.path.exists(KOMRADE_PRIV_KEY_FN): - with open(KOMRADE_PRIV_KEY_FN,'wb') as of: - of.write(KOMRADE_PRIV_KEY) - - if not os.path.exists(KOMRADE_PUB_KEY_FN): - with open(KOMRADE_PUB_KEY_FN,'wb') as of: - of.write(KOMRADE_PUB_KEY) - -# check_world_keys() - - -## CONNECTING - - - - -## utils - -def get_random_id(): - import uuid - return uuid.uuid4().hex - -def get_random_binary_id(): - import base64 - idstr = get_random_id() - return base64.b64encode(idstr.encode()) - - -def logger(): - import logging - handler = logging.StreamHandler() - formatter = logging.Formatter('[%(asctime)s]\n%(message)s\n') - handler.setFormatter(formatter) - logger = logging.getLogger(__file__) - logger.addHandler(handler) - logger.setLevel(logging.DEBUG) - return logger - -LOG = None - -def log(*x): - global LOG - if not LOG: LOG=logger().debug - - tolog=' '.join(str(_) for _ in x) - LOG(tolog) - - - -class NetworkStillConnectingError(OSError): pass - - - - - - - -class Persona(object): - - def __init__(self,name,api=None,node=None,create_if_missing=True): - self.name=name - self.log=log - self.privkey=None - self.pubkey=None - self.api=api - - self.can_receive = False - self.can_send = False - # self.can_login = False - self.external_keys_loaded = False - - - self.pubkey_enc = None - self.pubkey_decr = None - - # self._node=node - self.create_if_missing=create_if_missing - # self.log(f'>> Persona.__init__(name={name},create_if_missing={create_if_missing})') - - # load at least any local keys (non-async) - self.find_keys_local() - # self.login_or_register() - - - - def __repr__(self): - #pkeystr = '+loc' if self.has_public_key() else '' - #privkeystr = ' +key' if self.has_private_key() else '' - # ptypestr='acccount' if self.has_private_key() else 'contact' - ptypestr='loc' if not self.has_private_key() else 'keyloc' - - return f'{self.name} ({ptypestr})' - - def get_keyserver_pubkey(self): - Q=f'http://{KEYSERVER_ADDR}:{KEYSERVER_PORT}/pub' - - r = self.api.request(Q) - return r.content - # self.log('r =',r,b64encode(r.content)) - # pubkey_b64 = b64encode(r.content) - # return pubkey_b64 - - def get_externally_signed_pubkey(self): - Q=f'http://{KEYSERVER_ADDR}:{KEYSERVER_PORT}/get/{self.name}' - self.log('Q:',Q) - r = self.api.request(Q) - package_b64 = r.content - package = b64decode(package_b64) - self.log('package <--',package) - if not package: return (b'',b'',b'') - return package.split(BSEP) - # pubkey_b64, signed_pubkey_b64, server_signed_pubkey_b64 = package.split(BSEP) - - # signed_pubkey = b64encode(r.content) - # return (b64encode(pubkey), b64encode(signed_pubkey)) - - def set_externally_signed_pubkey(self): - import requests - Q=f'http://{KEYSERVER_ADDR}:{KEYSERVER_PORT}/add/{self.name}' #/{name}/{key} - - package = self.pubkey_b64 + BSEP + self.signed_pubkey_b64 - self.log('set_externally_signed_pubkey package -->',package) - - r = requests.post(Q, data=package) #{'name':self.name,'key':self.pubkey_b64}) - return r - - def has_private_key(self): - return self.privkey is not None - - def has_public_key(self): - return self.pubkey is not None - - - @property - async def node(self): - node = await self.api.node - return node - - - def load_keyserver_pubkey(self): - self.keyserver_pubkey_b64 = self.get_keyserver_pubkey() - self.keyserver_pubkey = b64decode(self.keyserver_pubkey_b64) - self.log('keyserver_pubkey =',self.keyserver_pubkey) - return self.keyserver_pubkey - - def load_external_keys(self): - if self.external_keys_loaded: return - - self.pubkey_ext_b64, self.signed_pubkey_ext_b64, self.server_signed_pubkey_ext_b64 = self.get_externally_signed_pubkey() - self.log('pubkey_ext_b64 =',self.pubkey_ext_b64) - self.log('signed_pubkey_ext_b64 =',self.signed_pubkey_ext_b64) - self.log('server_signed_pubkey_ext_b64 =',self.server_signed_pubkey_ext_b64) - self.external_keys_loaded = True - - def keyserver_name_exists(self): - user_missing = (self.pubkey_ext_b64 is b'' or self.signed_pubkey_ext_b64 is b'' or self.server_signed_pubkey_ext_b64 is b'') - return not user_missing - - - def meet(self,save_loc=True): - pubkey_ext = b64decode(self.pubkey_keyserver_verified) - self.log('pubkey_ext',pubkey_ext) - self.pubkey=pubkey_ext - self.log('setting self.pubkey to external value:',self.pubkey) - self.log('self.pubkey_64',self.pubkey_b64) - self.log('keyserver_verified',self.pubkey_keyserver_verified) - - with open(self.key_path_pub,'wb') as of: - of.write(self.pubkey_keyserver_verified) - - return {'success':'Met person as acquaintance'} - - def register(self): - # register - if self.create_if_missing: - self.gen_keys() - res = self.set_externally_signed_pubkey() - self.log('set_externally_signed_pubkey res =',res) - if res is None: - return {'error':'Could not set externally signed pubkey'} - else: - return {'success':'Created new pubkey'} - else: - return {'error':'No public key externally, but create_if_missing==False'} - - - # login, meet, or register - def boot(self): - # keyserver active? - keyserver_pubkey = self.keyserver_pubkey = self.load_keyserver_pubkey() - if keyserver_pubkey is None: - return {'error':'Cannot conntact keyserver'} - - # load external keys - self.load_external_keys() - - # user exists... - if self.keyserver_name_exists(): - - # if I claim to be this person - if self.privkey and self.pubkey_decr: - attempt = self.login() - self.log('login attempt ->',attempt) - return attempt - - # otherwise just meet them - attempt = self.meet() - self.log('meet attempt ->',attempt) - return attempt - - # user does not exist - attempt = self.register() - self.log('register attempt -->',attempt) - return attempt - - @property - def key_path_pub(self): - return os.path.join(KEY_PATH_PUB,'.'+self.name+'.loc') - - @property - def key_path_pub_enc(self): - return os.path.join(KEY_PATH_PUB,'.'+self.name+'.loc.box') - - - @property - def key_path_priv(self): - return os.path.join(KEY_PATH_PRIV,'.'+self.name+'.key') - - @property - def name_b64(self): - return b64encode(self.name.encode()) - - - @property - def privkey_b64(self): - return b64encode(self.privkey) - - @property - def pubkey_b64(self): - return b64encode(self.pubkey) if self.pubkey else b'' - - @property - def signed_pubkey_b64(self): - return self.sign(self.pubkey_b64) - # return self.encrypt(self.pubkey_b64, self.pubkey_b64) - - ## genearating keys - - # def gen_keys1(self): - # self.log('gen_keys()') - # keypair = GenerateKeyPair(KEY_PAIR_TYPE.EC) - # self.privkey = keypair.export_private_key() - # self.pubkey = keypair.export_public_key() - - # self.log(f'priv_key saved to {self.key_path_priv}') - # with open(self.key_path_priv, "wb") as private_key_file: - # private_key_file.write(self.privkey_b64) - - # with open(self.key_path_pub, "wb") as public_key_file: - # # save SIGNED public key - # public_key_file.write(self.pubkey_b64) - - # with open(self.key_path_pub_enc,'wb') as signed_public_key_file: - # # self.log('encrypted_pubkey_b64 -->',self.encrypted_pubkey_b64) - # pubkey_b64 = b64encode(self.pubkey) - # self.log('pubkey',self.pubkey) - # self.log('pubkey_b64',pubkey_b64) - - # encrypted_pubkey_b64 = self.encrypt(pubkey_b64, pubkey_b64, KOMRADE_PRIV_KEY) - # self.log('encrypted_pubkey_b64 -->',encrypted_pubkey_b64) - - # signed_public_key_file.write(encrypted_pubkey_b64) - - def gen_keys(self,passphrase=None): - """ - Generate private/public key pair - Secure that with passphrase - """ - - ## Generate key pair - self.log('gen_keys()') - keypair = GenerateKeyPair(KEY_PAIR_TYPE.EC) - self.privkey = keypair.export_private_key() - self.pubkey = keypair.export_public_key() - - ## Secure with passphrase - if passphrase is None: passphrase=input('Please protect account with passphrase:\n') - cell = SCellSeal(passphrase=passphrase) - keypair_b = self.privkey + BSEP + self.pubkey - - keypair_b_encr = cell.encrypt(keypair_b) - - - self.log(f'priv_key saved to {self.key_path_priv}') - with open(self.key_path_priv, "wb") as private_key_file: - private_key_file.write(self.privkey_b64) - - with open(self.key_path_pub, "wb") as public_key_file: - # save SIGNED public key - public_key_file.write(self.pubkey_b64) - - with open(self.key_path_pub_enc,'wb') as signed_public_key_file: - # self.log('encrypted_pubkey_b64 -->',self.encrypted_pubkey_b64) - pubkey_b64 = b64encode(self.pubkey) - self.log('pubkey',self.pubkey) - self.log('pubkey_b64',pubkey_b64) - - encrypted_pubkey_b64 = self.encrypt(pubkey_b64, pubkey_b64, KOMRADE_PRIV_KEY) - self.log('encrypted_pubkey_b64 -->',encrypted_pubkey_b64) - - signed_public_key_file.write(encrypted_pubkey_b64) - - - ## loading keys from disk - - def find_keys_local(self): - self.log(f'find_keys_local(path_pub={self.key_path_pub}, path_priv={self.key_path_priv})') - - if os.path.exists(self.key_path_priv): - with open(self.key_path_priv) as priv_f: - self.privkey=b64decode(priv_f.read()) - - # Load from encrypted? - if os.path.exists(self.key_path_pub_enc) and self.privkey: - with open(self.key_path_pub_enc) as pub_f: - self.pubkey_enc=pub_f.read() - self.pubkey_decr=self.decrypt(self.pubkey_enc, KOMRADE_PUB_KEY) - # self.pubkey=self.pubkey_decr - self.log('loaded self.pubkey from enc',self.pubkey) - self.can_receive = True - self.can_send = True - - # load from nonencrypted pubkey? - if os.path.exists(self.key_path_pub): - with open(self.key_path_pub) as pub_f: - self.pubkey=b64decode(pub_f.read()) - self.log('loaded self.pubkey from UNenc',self.pubkey) - self.can_receive = True - - @property - def pubkey_keyserver_verified(self): - # test if remote data agrees - if not hasattr(self,'_keyserver_verified'): - self._keyserver_verified = self.verify(self.server_signed_pubkey_ext_b64, self.keyserver_pubkey_b64) - return self._keyserver_verified - - def login(self): - # test if local data present - if not self.pubkey or not self.pubkey_decr: - return {'error':'Public keys not present'} - - # test if local data agrees - self.log('self.pubkey',self.pubkey_b64) - self.log('self.pubkey_decr',self.pubkey_decr) - if self.pubkey_b64 != self.pubkey_decr: - return {'error':'Public keys do not match'} - - # test if remote data agrees - keyserver_verified = self.pubkey_keyserver_verified - if keyserver_verified is None: - return {'error':'Keyserver verification failed'} - - # test if pubkey match - enc_match = self.pubkey_decr == keyserver_verified - if enc_match: - return {'success':'Keys matched'} - return {'error':'Keys did not match'} - - - - - - - ## E/D/S/V - - def encrypt(self,msg_b64,for_pubkey_b64, privkey_b64=None): - self.log('encrypt()',msg_b64,for_pubkey_b64,privkey_b64) - - privkey = b64decode(privkey_b64) if privkey_b64 else self.privkey - # handle verification failure - for_pubkey = b64decode(for_pubkey_b64) - encrypted_msg = SMessage(privkey, for_pubkey).wrap(msg_b64) - return b64encode(encrypted_msg) - - def decrypt(self,encrypted_msg_b64,from_pubkey_b64, privkey_b64=None): - privkey = b64decode(privkey_b64) if privkey_b64 else self.privkey - - # handle verification failure - from_pubkey = b64decode(from_pubkey_b64) - encrypted_msg = b64decode(encrypted_msg_b64) - decrypted_msg = SMessage(privkey, from_pubkey).unwrap(encrypted_msg) - return decrypted_msg - - def sign(self,msg_b64, privkey=None): - if not privkey: privkey=self.privkey - signed_msg = b64encode(ssign(privkey, msg_b64)) - return signed_msg - - def verify(self,signed_msg_b64,pubkey_b64=None): - if pubkey_b64 is None: pubkey_b64=self.pubkey_b64 - - self.log('verify() signed_msg_b64 =',signed_msg_b64) - self.log('verify() pubkey_b64 =',pubkey_b64) - signed_msg = b64decode(signed_msg_b64) - public_key = b64decode(pubkey_b64) - - self.log('verify() signed_msg =',signed_msg) - self.log('verify() public_key =',public_key) - - try: - verified_msg = sverify(public_key, signed_msg) - return verified_msg - except ThemisError as e: - print('!!',e) - return None - - - - - @property - def uri_inbox(self): - return P2P_PREFIX_INBOX+self.name.encode() - - @property - def uri_outbox(self): - return P2P_PREFIX_OUTBOX+self.name.encode() - - @property - def app_pubkey_b64(self): - return KOMRADE_PUB_KEY - - - - - ## POSTING/SENDING MSGS - - async def post(self,encrypted_payload_b64,to_person): - # double wrap - double_encrypted_payload = self.encrypt(encrypted_payload_b64, to_person.pubkey_b64, KOMRADE_PRIV_KEY) - self.log('double_encrypted_payload =',double_encrypted_payload) - - post_id = get_random_binary_id() #get_random_id().encode() - node = await self.node - - uri_post = P2P_PREFIX_POST + post_id - res = await node.set(uri_post, double_encrypted_payload) - self.log('result of post() =',res) - - return uri_post - - async def send(self,msg_b,to,from_person=None): - """ - 1) [Encrypted payload:] - 1) Timestamp - 2) Public key of sender - 3) Public key of recipient - 4) AES-encrypted Value - 2) [Decryption tools] - 1) AES-decryption key - 2) AES decryption IV value - 5) Signature of value by author - """ - - if type(msg_b)==str: msg_b=msg_b.encode() - msg_b64=b64encode(msg_b) - - # encrypt and sign - to_person = to - if from_person is None: from_person = self - encrypted_payload = from_person.encrypt(msg_b64, to_person.pubkey_b64) - signed_encrypted_payload = from_person.sign(encrypted_payload) - - # package - time_b64 = b64encode(str(time.time()).encode()) - WDV_b64 = b64encode(BSEP.join([ - signed_encrypted_payload, - from_person.pubkey_b64, - from_person.name_b64, - time_b64])) - self.log('WDV_b64 =',WDV_b64) - - # post - post_id = await self.post(WDV_b64, to_person) - self.log('post_id <-',post_id) - - # add to inbox - res = await to_person.add_to_inbox(post_id) - self.log('add_to_inbox <-',res) - - # add to outbox? - # pass - - - - - - - - - - - async def load_inbox(self,decrypt_msg_uri=False,last=None): - node = await self.node - encrypted_inbox_idstr_b64 = await node.get(self.uri_inbox) - self.log('encrypted_inbox_idstr_b64 =',encrypted_inbox_idstr_b64) - if encrypted_inbox_idstr_b64 is None: return [] - - # inbox_idstr = self.decrypt(encrypted_inbox_idstr_b64, self.app_pubkey_b64) - # self.log('decrypted inbox_idstr =',inbox_idstr) - # decrypt! - - encrypted_inbox_idstr = b64decode(encrypted_inbox_idstr_b64) - self.log('encrypted_inbox_idstr =',encrypted_inbox_idstr) - - inbox_ids = encrypted_inbox_idstr.split(BSEP) if encrypted_inbox_idstr is not None else [] - self.log('inbox_ids =',inbox_ids) - - if decrypt_msg_uri: - inbox_ids = [self.decrypt(enc_msg_id_b64,KOMRADE_PUB_KEY) for enc_msg_id_b64 in inbox_ids] - self.log('inbox_ids decrypted =',inbox_ids) - - return inbox_ids[:last] - - async def add_to_inbox(self,msg_uri,inbox_sofar=None): - # encrypt msg id so only inbox owner can resolve the pointer - self.log('unencrypted msg uri:',msg_uri) - encrypted_msg_uri = self.encrypt(msg_uri, self.pubkey_b64, KOMRADE_PRIV_KEY) - self.log('encrypted msg uri:',encrypted_msg_uri) - - # get current inbox - if inbox_sofar is None: inbox_sofar=await self.load_inbox() - self.log('inbox_sofar:',inbox_sofar) - - # add new value - new_inbox = inbox_sofar + [encrypted_msg_uri] - new_inbox_b = BSEP.join(new_inbox) - self.log('new_inbox_b:',new_inbox_b) - - new_inbox_b64 = b64encode(new_inbox_b) - - self.log('new_inbox_b64:',new_inbox_b64) - - # set on net - node = await self.node - await node.set(self.uri_inbox,new_inbox_b64) - - new_length = len(new_inbox) - return {'success':'Inbox length increased to %s' % new_length} - #return {'error':'Could not append data'} - - - async def add_to_outbox(self): - """ - Do not store on server! - """ - pass - - async def read_inbox(self,uri_inbox=None): - if uri_inbox is None: uri_inbox = P2P_PREFIX_INBOX+self.name.encode() - node = await self.node - inbox_ids = await node.get(uri_inbox) - if inbox_ids is not None: - inbox_ids = inbox_ids.split(BSEP) - self.log('found inbox IDs:',inbox_ids) - - msgs_toread = [self.read_msg(msg_id) for msg_id in inbox_ids] - msgs = await asyncio.gather(*msgs_toread) - self.log('read_inbox() msgs = ',msgs) - return msgs - return [] - - async def read_outbox(self,uri_outbox=None): - if uri_outbox is None: uri_outbox = P2P_PREFIX_OUTBOX+self.name.encode() - return await self.read_inbox(uri_outbox) - - - async def read_msg(self,msg_id): - self.log(f'Persona.read_msg({msg_id}) ?') - uri_msg=P2P_PREFIX_POST+msg_id - node = await self.node - - res = await node.get(uri_msg) - self.log('res = ',res) - if res is not None: - double_encrypted_payload_b64 = res - single_encrypted_payload = self.decrypt(double_encrypted_payload_b64, KOMRADE_PUB_KEY) - self.log('GOT ENRYPTED PAYLOAD:',single_encrypted_payload) - - signed_encrypted_payload_b64,from_pubkey_b64,name_b64,time_b64 = single_encrypted_payload.split(BSEP) - self.log('signed_encrypted_payload =',signed_encrypted_payload_b64) - self.log('from_pubkey_b64 =',from_pubkey_b64) - self.log('time_b64 =',time_b64) - - from_name = b64decode(name_b64).decode() - self.log('from_name =',from_name) - timestamp = b64decode(time_b64).decode() - tmpP = Persona(from_name) - await tmpP.boot() - from_pubkey_b64_acc_to_name = tmpP.pubkey_b64 - assert from_pubkey_b64==from_pubkey_b64_acc_to_name - - encrypted_payload_b64 = self.verify(signed_encrypted_payload_b64, from_pubkey_b64) - self.log('encrypted_payload_b64 =',encrypted_payload_b64) - - payload = self.decrypt(encrypted_payload_b64, from_pubkey_b64) - self.log('payload =',payload) - return { - 'success':True, - 'content':payload, - 'from_name':from_name, - 'from_pubkey_b64':from_pubkey_b64, - 'timestamp':timestamp - } - return {'error':'Unknown'} - - - -def run_multiple_tasks(tasks): - async def _go(tasks): - res = await asyncio.gather(*tasks, return_exceptions=True) - return res - return asyncio.get_event_loop().run_until_complete(_go(tasks)) - -async def main(): - - # start node - from kademlia.network import Server - #from p2p_api import - PORT_LISTEN = 5969 - - # NODES_PRIME = [("128.232.229.63",8467), ("68.66.241.111",8467)] - NODES_PRIME = [("128.232.229.63",8467)] - - node = Server(log=log) - await node.listen(PORT_LISTEN) - await node.bootstrap(NODES_PRIME) - - marx = Persona('marx',node=node) - elon = Persona('elon2',node=node) - world = Persona('world',node=node) - await world.boot() - - - await marx.boot() - await elon.boot() - - # await marx.send(b'Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! Secret message 2! ',to=elon) - - - # await elon.read_inbox() - - await marx.send(b'A specter is haunting the internet',to=world) - await elon.send(b'My rockets explode and so will your mind',to=world) - await elon.send(b'My rockets explode and so will your mind',to=world) - - await world.read_inbox() - - return True - - -if __name__=='__main__': - asyncio.run(main()) - - - - - - - - - - - - - - diff --git a/backend/caller.py b/backend/caller.py deleted file mode 100644 index 19e16ca..0000000 --- a/backend/caller.py +++ /dev/null @@ -1,205 +0,0 @@ -from pythemis.skeygen import KEY_PAIR_TYPE, GenerateKeyPair -from pythemis.smessage import SMessage, ssign, sverify -from pythemis.skeygen import GenerateSymmetricKey -from pythemis.scell import SCellSeal -from pythemis.exception import ThemisError -import getpass,os -from crypt import Crypt -from the_operator import TheOperator - -# paths -PATH_KOMRADE = os.path.abspath(os.path.join(os.path.expanduser('~'),'.komrade')) -PATH_CALLER = os.path.join(PATH_KOMRADE,'.caller') -PATH_CALLER_PUBKEY = os.path.join(PATH_CALLER,'.ca.key.pub.encr') -PATH_CALLER_PRIVKEY = os.path.join(PATH_CALLER,'.ca.key.priv.encr') -PATH_CRYPT_KEYS = os.path.join(PATH_CALLER,'.ca.db.keys.crypt') -PATH_CRYPT_DATA = os.path.join(PATH_CALLER,'.ca.db.data.encr') - - - -# class HelloOperator(object): -# def __init__(self,op=None): -# # for now -# self.op = TheOperator() - -# def create_keys(self,name): -# return self.op.create_keys(name) - -# def exists(self,*x,**y): return self.op.exists(*x,**y) - - -class Caller(object): - - ### INIT CODE - def __init__(self,name): - self.name=name - self.op = TheOperator() - - - def log(self,*x): print(*x) - - - ## CRYPT BASICS - - @property - def crypt_cell(self): - pass - - @property - def crypt_keys(self): - if not hasattr(self,'_crypt_keys'): - self._crypt_keys = Crypt(fn=PATH_CRYPT_KEYS) - return self._crypt_keys - - @property - def crypt_data(self): - if not hasattr(self,'_crypt_data'): - self._crypt_data = Crypt(fn=PATH_CRYPT_DATA) - return self._crypt_data - - - - ### CREATION OF KEYS - - # Get key de-cryptors - def gen_pass_keycell(self,pass_phrase,q_name='Read permissions?'): - if pass_phrase is None: - pass_key = GenerateSymmetricKey() - pass_cell = SCellSeal(key=pass_key) - else: - if pass_phrase is True: pass_phrase=getpass.getpass(f'Enter pass phrase [{q_name}]: ') - pass_key = None - pass_cell = SCellSeal(passphrase=pass_phrase) - return (pass_key, pass_cell) - - - def create_keys(self,pubkey_pass = None, privkey_pass = None, adminkey_pass = None): - # Get keys back from The Operator - res = self.op.create_keys(self.name) - self.log('create_keys() res from Operator? <-',res) - assert type(res)==tuple and len(res)==3 - (pubkey_decr, privkey_decr, adminkey_decr) = res - - # Get new encryptors - pubkey_passkey,pubkey_passcell = self.gen_pass_keycell(pubkey_pass,q_name='Permission key, to find') - privkey_passkey,privkey_passcell = self.gen_pass_keycell(privkey_pass,q_name='Permission key, to read') - adminkey_passkey,adminkey_passcell = self.gen_pass_keycell(adminkey_pass,q_name='Permission key, to admin') - - # double-encrypt what was received - pubkey_decr_encr = pubkey_passcell.encrypt(pubkey_decr) - privkey_decr_encr = privkey_passcell.encrypt(privkey_decr) - adminkey_decr_encr = adminkey_passcell.encrypt(adminkey_decr) - - # store double encrypted keys - self.crypt_keys.set(self.name,pubkey_decr_encr,prefix='/pub_decr_encr/') - self.crypt_keys.set(pubkey_decr,privkey_decr_encr,prefix='/priv_decr_encr/') - self.crypt_keys.set(privkey_decr,adminkey_decr_encr,prefix='/admin_decr_encr/') - - # store decryption keys if not passworded? - if pubkey_passkey: self.crypt_keys.set(self.name,pubkey_passkey,prefix='/pub_decr_decr/') - if privkey_passkey: self.crypt_keys.set(pubkey_decr,privkey_passkey,prefix='/priv_decr_decr/') - if adminkey_passkey: self.crypt_keys.set(privkey_decr,adminkey_passkey,prefix='/admin_decr_decr/') - - # done? - - - - ## MAGIC KEY ATTRIBUTES - - @property - def pubkey_decr_encr(self): - return self.crypt_keys.get(self.name,prefix='/pub_decr_encr/') - - @property - def privkey_decr_encr(self): - return self.crypt_keys.get(self.pubkey_decr,prefix='/priv_decr_encr/') - - @property - def pubkey_decr_encr(self): - return self.crypt_keys.get(self.privkey_decr,prefix='/admin_decr_encr/') - - - - - - # loading keys back - - @property - def pubkey_decr_cell(self): - decr_key = self.crypt_keys.get(self.name,prefix='/pub_decr_encr/') - if not decr_key: - if not self.passphrase: return - decr_cell = SCellSeal(passphrase=self.passphrase) - else: - decr_cell = SCellSeal(key=decr_key) - return decr_cell - - - @property - def privkey_decr_cell(self): - decr_key = self.crypt_keys.get(self.name,prefix='/priv_decr_encr/') - if not decr_key: - if not self.passphrase: return - decr_cell = SCellSeal(passphrase=self.passphrase) - else: - decr_cell = SCellSeal(key=decr_key) - return decr_cell - - @property - def adminkey_decr_cell(self): - decr_key = self.crypt_keys.get(self.name,prefix='/admin_decr_encr/') - if not decr_key: - if not self.passphrase: return - decr_cell = SCellSeal(passphrase=self.passphrase) - else: - decr_cell = SCellSeal(key=decr_key) - return decr_cell - - @property - def pubkey_decr(self): - return self.pubkey_decr_cell.decrypt(self.pubkey_decr_encr) - - @property - def privkey_decr(self): - return self.privkey_decr_cell.decrypt(self.privkey_decr_encr) - - @property - def adminkey_decr(self): - return self.adminkey_decr_cell.decrypt(self.adminkey_decr_encr) - - - - - - - ### HIGH LEVEL - # Do I exist? - - def exists(self): - return self.op.exists(self.name) - - def register(self,passphrase = None, as_group=False): - if self.exists(): - self.log('ERROR: user already exists') - return - - if not passphrase: passphrase = getpass.getpass('Enter password for new account: ') - self.passphrase=passphrase - - if as_group: - self.create_keys(adminkey_pass=passphrase) - else: - self.create_keys(privkey_pass=passphrase, adminkey_pass=passphrase) - - - def login(self,passphrase = None): - if not passphrase: passphrase = getpass.getpass('Enter login password: ') - self.passphrase = passphrase - if not - - - -if __name__ == '__main__': - caller = Caller('elon2') - - caller.register() \ No newline at end of file diff --git a/docs/cryptosystems-Kademlia.png b/docs/cryptosystems-Kademlia.png new file mode 100644 index 0000000..2e34400 Binary files /dev/null and b/docs/cryptosystems-Kademlia.png differ diff --git a/docs/cryptosystems-Operator.png b/docs/cryptosystems-Operator.png index f5821cc..ef2cdbe 100644 Binary files a/docs/cryptosystems-Operator.png and b/docs/cryptosystems-Operator.png differ diff --git a/docs/cryptosystems.drawio b/docs/cryptosystems.drawio index ec435a5..0457796 100644 --- a/docs/cryptosystems.drawio +++ b/docs/cryptosystems.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/komrade/__init__.py b/komrade/__init__.py new file mode 100644 index 0000000..cbf9b45 --- /dev/null +++ b/komrade/__init__.py @@ -0,0 +1,17 @@ +### +# Define some basic things while we're here + +class KomradeException(Exception): pass + +# make sure komrade is on path +import sys,os +sys.path.append(os.path.dirname(__file__)) + +import inspect +class Logger(object): + def log(self,*x): + curframe = inspect.currentframe() + calframe = inspect.getouterframes(curframe, 2) + mytype = type(self).__name__ + caller = calframe[1][3] + print(f'\n[{mytype}.{caller}()]',*x) diff --git a/api/__init__.py b/komrade/api/__init__.py similarity index 100% rename from api/__init__.py rename to komrade/api/__init__.py diff --git a/api/api.py b/komrade/api/api.py similarity index 100% rename from api/api.py rename to komrade/api/api.py diff --git a/komrade/api/persona.py b/komrade/api/persona.py new file mode 100644 index 0000000..95d8797 --- /dev/null +++ b/komrade/api/persona.py @@ -0,0 +1,81 @@ +# mine imports +import os,sys; sys.path.append(os.path.abspath(os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__),'..')),'..'))) +from komrade.backend.caller import Caller +from komrade import KomradeException,Logger + +# other imports +import asyncio,os,time,sys,logging,getpass +from pythemis.skeygen import KEY_PAIR_TYPE, GenerateKeyPair +from pythemis.smessage import SMessage, ssign, sverify +from pythemis.exception import ThemisError +from pythemis.scell import SCellSeal +from base64 import b64decode,b64encode +from pathlib import Path + + +class Model(Logger): pass + +class UserAlreadyExists(KomradeException): pass + +class Persona(Model): + def __init__(self, name, is_group=False): + self.name = name + self.is_group=is_group + + @property + def op(self): + return Caller(self.name) + + + ### + # MAJOR OPERATIONS + ### + + def register(self,passphrase = None): + """ + Register this new persona. + Protect keys according to a passphrase. + If group, only admin key pass-protected; + if individual, all keys pass-protected. + """ + + # Does user already exist? + if self.op.exists(): raise UserAlreadyExists('User already exists') + + # Get passphrase + if not passphrase: passphrase = getpass.getpass('Enter password for new account: ') + + # Create + if self.is_group: + self.op.create_keys(adminkey_pass=passphrase) + else: + self.op.create_keys(privkey_pass=passphrase,adminkey_pass=passphrase) + + + def login(self,passphrase = None): + # Get passphrase + if not passphrase: passphrase = getpass.getpass('Enter login password: ') + + # Get my decryption keys + if self.is_group: + keychain_decr = self.op.keychain_decr(adminkey_pass=passphrase) + else: + keychain_decr = self.op.keychain_decr(privkey_pass=passphrase,adminkey_pass=passphrase) + + print(keychain_decr) + + + + + + +if __name__ == '__main__': + import random + idnum = random.choice(list(range(1000))) + persona = Persona('elon'+str(idnum)) + print('\n\n\nREGISTERING\n\n\n') + persona.register(passphrase='bb') + + print('\n\n\nLOGGING IN\n\n\n') + + persona.login(passphrase='bb') \ No newline at end of file diff --git a/api/persona0.py b/komrade/api/persona0.py similarity index 100% rename from api/persona0.py rename to komrade/api/persona0.py diff --git a/api/run.sh b/komrade/api/run.sh similarity index 100% rename from api/run.sh rename to komrade/api/run.sh diff --git a/komrade/backend/__init__.py b/komrade/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/komrade/backend/caller.py b/komrade/backend/caller.py new file mode 100644 index 0000000..3c9f07b --- /dev/null +++ b/komrade/backend/caller.py @@ -0,0 +1,93 @@ +import os,sys; sys.path.append(os.path.abspath(os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__),'..')),'..'))) +from komrade.backend.crypt import Crypt +from komrade.backend.the_operator import TheOperator +from komrade.backend.keymaker import Keymaker +from komrade import KomradeException,Logger + + +from pythemis.skeygen import KEY_PAIR_TYPE, GenerateKeyPair +from pythemis.smessage import SMessage, ssign, sverify +from pythemis.skeygen import GenerateSymmetricKey +from pythemis.scell import SCellSeal +from pythemis.exception import ThemisError +import getpass,os + + +# paths +PATH_KOMRADE = os.path.abspath(os.path.join(os.path.expanduser('~'),'.komrade')) +PATH_CALLER = os.path.join(PATH_KOMRADE,'.caller') +PATH_CALLER_PUBKEY = os.path.join(PATH_CALLER,'.ca.key.pub.encr') +PATH_CALLER_PRIVKEY = os.path.join(PATH_CALLER,'.ca.key.priv.encr') +PATH_CRYPT_KEYS = os.path.join(PATH_CALLER,'.ca.db.keys.crypt') +PATH_CRYPT_DATA = os.path.join(PATH_CALLER,'.ca.db.data.encr') + + + +# class HelloOperator(object): +# def __init__(self,op=None): +# # for now +# self.op = TheOperator() + +# def create_keys(self,name): +# return self.op.create_keys(name) + +# def exists(self,*x,**y): return self.op.exists(*x,**y) + + +class Caller(Keymaker): + + ### INIT CODE + def __init__(self,name): + self.name=name + self.op = TheOperator() + + ## CRYPT BASICS + + @property + def crypt_cell(self): + pass + + ### CREATION OF KEYS + def exists(self): + return self.op.exists(self.name) + + + def create_keys(self,pubkey_pass = None, privkey_pass = None, adminkey_pass = None): + # Get keys back from The Operator + res = self.op.create_keys(self.name) + self.log('create_keys() res from Operator? <-',res) + assert type(res)==tuple and len(res)==3 + (pubkey_decr, privkey_decr, adminkey_decr) = res + + # double-encrypt what was received + pubkey_decr_decr_key,pubkey_decr_decr_cell = self.pubkey_decr_decr_keycell(passphrase=pubkey_pass) + self.log('pubkey_decr_decr_key <--',pubkey_decr_decr_key) + self.log('pubkey_decr_decr_cell <--',pubkey_decr_decr_cell) + self.log('pubkey_decr <--',pubkey_decr) + pubkey_decr_encr = pubkey_decr_decr_cell.encrypt(pubkey_decr) + self.log('pubkey_decr_encr <--',pubkey_decr_encr) + + # privkey_decr_encr = privkey_passcell.encrypt(privkey_decr) + # self.log('pubkey_decr_encr <--',pubkey_decr_encr) + + # adminkey_decr_encr = adminkey_passcell.encrypt(adminkey_decr) + # self.log('pubkey_decr_encr <--',pubkey_decr_encr) + + # store double encrypted keys + self.crypt_keys.set(self.name,pubkey_decr_encr,prefix='/pub_decr_encr/') + # self.crypt_keys.set(pubkey_decr,privkey_decr_encr,prefix='/priv_decr_encr/') + # self.crypt_keys.set(privkey_decr,adminkey_decr_encr,prefix='/admin_decr_encr/') + + # store decryption keys if not passworded? + if pubkey_decr_decr_key: self.crypt_keys.set(self.name,pubkey_decr_decr_key,prefix='/pub_decr_decr_key/') + # if privkey_passkey: self.crypt_keys.set(pubkey_decr,privkey_passkey,prefix='/priv_decr_decr_key/') + # if adminkey_passkey: self.crypt_keys.set(privkey_decr,adminkey_passkey,prefix='/admin_decr_decr_key/') + + # done? + + + +if __name__ == '__main__': + caller = Caller('elon2') + + # caller.register() \ No newline at end of file diff --git a/backend/crypt.py b/komrade/backend/crypt.py similarity index 92% rename from backend/crypt.py rename to komrade/backend/crypt.py index 46d51b9..ac6568f 100644 --- a/backend/crypt.py +++ b/komrade/backend/crypt.py @@ -11,19 +11,24 @@ from pythemis.skeygen import GenerateSymmetricKey from pythemis.scell import SCellSeal from pythemis.exception import ThemisError import zlib +from komrade import KomradeException,Logger +LOG_GET_SET = False -class Crypt(object): - def log(self,*x): print(*x) +class Crypt(Logger): def __init__(self,name=None,fn=None,cell=None): if not name and fn: name=os.path.basename(fn).replace('.','_') self.name,self.fn,self.cell = name,fn,cell self.store = FilesystemStore(self.fn) + + def log(self,*x): + if LOG_GET_SET: + super().log(*x) def hash(self,binary_data): return hashlib.sha256(binary_data).hexdigest() diff --git a/backend/ether.py b/komrade/backend/ether.py similarity index 100% rename from backend/ether.py rename to komrade/backend/ether.py diff --git a/komrade/backend/keymaker.py b/komrade/backend/keymaker.py new file mode 100644 index 0000000..6f65cde --- /dev/null +++ b/komrade/backend/keymaker.py @@ -0,0 +1,205 @@ +class Keymaker(Logger): + + + ### BASE STORAGE + @property + def crypt_keys(self): + if not hasattr(self,'_crypt_keys'): + self._crypt_keys = Crypt(fn=PATH_CRYPT_KEYS) + return self._crypt_keys + + @property + def crypt_data(self): + if not hasattr(self,'_crypt_data'): + self._crypt_data = Crypt(fn=PATH_CRYPT_DATA) + return self._crypt_data + + + ### STARTING WITH MOST ABSTRACT + + ### (1) Final keys + def getkey(self, uri, passphrases={}, keychain_encr={}, keychain_decr={}, keyname=''): + if not keyname: return + key = None + + # if the decryption keys have been provided to me + if keychain_decr: + # get the relevant decryption key + key_decr = keychain_encr.get(f'{keyname}key_decr'): + + # see if I have the right encrypted key + key_encr = self.getkey_encr(uri, passphrases=passphrases, keychain_decr=keychain_decr, keyname=keyname) + + # conversely, if the encryption keys have been provided to me + elif keychain_encr: + # get the relevant encryption key + key_encr = keychain_encr.get(f'{keyname}key_decr'): + + # see if I have the right encrypted key + key_decr = self.getkey_decr(uri, passphrases=passphrases, keychain_decr=keychain_decr, keyname=keyname) + + # then, once I have both: + if not keychain_decr and not keychain_encr: return + try: + key = SCellSeal(key=key_decr).decrypt(key_encr) + except ThemisError as e: + self.log('key recovery failed',e) + return key + + def pubkey(self, **kwargs): + return self.getkey(uri=self.name,keyname='pub',**kwargs) + def privkey(self, **kwargs): + return self.getkey(uri=self.pubkey(**kwargs),keyname='priv',**kwargs) + def adminkey(self, **kwargs): + return self.getkey(uri=self.privkey(**kwargs),keyname='admin',**kwargs) + + + + ## (2A) ENCRYPTED KEYS + def getkey_encr(self, uri, passphrases={}, keychain_decr={}, keyname=''): + return self.crypt_keys.get(uri,prefix=f'/{keyname}_encr/') + def pubkey_encr(self, **kwargs): + return self.getkey_encr(uri=self.name,keyname='pub',**kwargs) + def privkey_encr(self, **kwargs): + return self.getkey(uri=self.pubkey_encr(**kargs),keyname='priv',**kwargs) + def adminkey_encr(self, **kwargs): + return self.getkey(uri=self.privkey_encr(**kargs),keyname='admin',**kwargs) + + + ### (2B) DECRYPTED KEYS + def getkey_decr(self, uri, passphrases={}, keychain_encr={}, keyname=''): + key_loaded = self.crypt_keys.get(uri,prefix=f'/{keyname}_decr/') + if key_loaded: return key_loaded + + # otherwise, get it by decrypting its components? + return self.buildkey + + + + def pubkey_decr(self, **kwargs): + return self.getkey_decr(uri=self.name,keyname='pub',**kwargs) + def privkey_decr(self, **kwargs): + return self.getkey(uri=self.pubkey_decr(**kargs),keyname='priv',**kwargs) + def adminkey_decr(self, **kwargs): + return self.getkey(uri=self.privkey_decr(**kargs),keyname='admin',**kwargs) + + def buildkey_decr(self, passphrases={}, keyname='pub'): + # need two pieces: its encrypted part and its decrypted part + key_decr_encr = self.getkey_decr_encr(keyname=keyname) + + key_decr_decr_key,pubkey_decr_decr_cell = self.getkey_decr_decr_keycell(passphrase,keyname=keyname) + + self.log(f'about to decrypt {pubkey_decr_encr} with cell {pubkey_decr_decr_cell}') + try: + pubkey_decr = pubkey_decr_decr_cell.decrypt(pubkey_decr_encr) + except ThemisError as e: + self.log('!!',e) + exit() + + self.log('pubkey_decr <--',pubkey_decr) + return pubkey_decr + + def pubkey_decr(self, passphrases={}): + pubkey_decr_encr = self.pubkey_decr_encr() + pubkey_decr_decr_key,pubkey_decr_decr_cell = self.pubkey_decr_decr_keycell(passphrase) + + self.log(f'about to decrypt {pubkey_decr_encr} with cell {pubkey_decr_decr_cell}') + try: + pubkey_decr = pubkey_decr_decr_cell.decrypt(pubkey_decr_encr) + except ThemisError as e: + self.log('!!',e) + exit() + + self.log('pubkey_decr <--',pubkey_decr) + return pubkey_decr + + def privkey_decr(self, passphrases={}): + privkey_decr_encr = self.privkey_decr_encr() + privkey_decr_decr_cell = self.privkey_decr_decr_cell(passphrase) + privkey_decr = privkey_decr_decr_cell.decrypt(privkey_decr_encr) + return privkey_decr + + def adminkey_decr(self, passphrases={}): + adminkey_decr_encr = self.adminkey_decr_encr() + adminkey_decr_decr_cell = self.adminkey_decr_decr_cell(passphrase) + adminkey_decr = adminkey_decr_decr_cell.decrypt(adminkey_decr_encr) + return adminkey_decr + + def keychain_decr(self, pubkey_pass = None, privkey_pass = None, adminkey_pass = None): + return { + 'pubkey_decr':self.pubkey_decr(pubkey_pass), + 'privkey_decr':self.privkey_decr(privkey_pass), + 'adminkey_decr':self.adminkey_decr(adminkey_pass) + } + + ## MAGIC KEY ATTRIBUTES + # loading keys back + + ### DECR DECR KEYCELL + + # Get key de-cryptors + def genkey_pass_keycell(self,pass_phrase,q_name='Read permissions?'): + if pass_phrase is None: + pass_key = GenerateSymmetricKey() + pass_cell = SCellSeal(key=pass_key) + else: + if pass_phrase is True: pass_phrase=getpass.getpass(f'Enter pass phrase [{q_name}]: ') + pass_key = None + pass_cell = SCellSeal(passphrase=pass_phrase) + + self.log(f'pass_key [{q_name}] <--',pass_key) + self.log(f'pass_cell [{q_name}] <--',pass_cell) + return (pass_key, pass_cell) + + + def getkey_decr_decr_keycell(self, passphrases={}, keyname='pub'): + # get or make + decr_key = None + decr_cell = None + + if passphrase: + decr_key=None + decr_cell = SCellSeal(passphrase=passphrase) + return (decr_key,decr_cell) + + # if I have one stored? + decr_key = self.crypt_keys.get(self.name,prefix=f'/{keyname}_decr_decr_key/') + if decr_key: + decr_cell = SCellSeal(key=decr_key) + return (decr_key,decr_cell) + + # if I need to generate one + if not decr_cell: + return self.genkey_pass_keycell() + + return (decr_key,decr_cell) + + def pubkey_decr_decr_keycell(self,passphrases={}): + return self.getkey_decr_decr_keycell(passphrase=passphrase, keyname='pub') + def privkey_decr_decr_keycell(self,passphrases={}): + return self.getkey_decr_decr_keycell(passphrase=passphrase, keyname='priv') + def adminkey_decr_decr_keycell(self,passphrases={}): + return self.getkey_decr_decr_keycell(passphrase=passphrase, keyname='admin') + + + + ### DECR ENCR KEYS + def getkey_decr_encr(self,crypt_key = None,keyname='pub'): + if not crypt_key: crypt_key = self.name + key_decr_encr = self.crypt_keys.get(self.name,prefix=f'/{keyname}_decr_encr/') + self.log(f'{keyname}key_decr_encr <--',key_decr_encr) + return key_decr_encr + def pubkey_decr_encr(self,passphrases={}): + return self.getkey_decr_encr(crypt_key=self.name, keyname='pub') + def privkey_decr_encr(self,passphrases={}): + pubkey_decr = self.pubkey_decr(passphrase=passphrase) + return self.getkey_decr_encr(crypt_key=pubkey_decr, keyname='priv') + def adminkey_decr_encr(self,passphrases={}): + privkey_decr=self.privkey_decr(passphrase=passphrase) + return self.getkey_decr_encr(crypt_key=privkey_decr, keyname='admin') + + + + ### DECR KEYS + + diff --git a/backend/keyserver0.py b/komrade/backend/keyserver0.py similarity index 100% rename from backend/keyserver0.py rename to komrade/backend/keyserver0.py diff --git a/backend/the_operator.py b/komrade/backend/the_operator.py similarity index 76% rename from backend/the_operator.py rename to komrade/backend/the_operator.py index 0175fed..0590496 100644 --- a/backend/the_operator.py +++ b/komrade/backend/the_operator.py @@ -2,7 +2,9 @@ There is only one operator! Running on node prime. """ -import os,sys +import os,sys; sys.path.append(os.path.abspath(os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__),'..')),'..'))) +from komrade.backend.crypt import Crypt +from komrade.backend.keymaker import Keymaker from flask import Flask from flask_classful import FlaskView from pythemis.skeygen import KEY_PAIR_TYPE, GenerateKeyPair @@ -11,6 +13,7 @@ from pythemis.skeygen import GenerateSymmetricKey from pythemis.scell import SCellSeal from pythemis.exception import ThemisError from base64 import b64encode,b64decode +from komrade import KomradeException,Logger import getpass PATH_HERE = os.path.dirname(__file__) sys.path.append(PATH_HERE) @@ -35,22 +38,18 @@ if not os.path.exists(PATH_OPERATOR): os.makedirs(PATH_OPERATOR) -class TheOperator(object): +class TheOperator(Keymaker): """ The operator. """ - def __init__(self): + def __init__(self, passphrase = None): """ Boot up the operator. Requires knowing or setting a password of memory. """ - # Establish encryption/decryption cell - self.cell = SCellSeal(passphrase=getpass.getpass('What is the password of memory? ')) - # Do I have my keys? - have_keys = self.check_keys() # If not, forge them -- only once! if not have_keys: self.forge_keys() @@ -61,31 +60,25 @@ class TheOperator(object): # That's it! - @property - def crypt_keys(self): - if not hasattr(self,'_crypt_keys'): - self._crypt_keys = Crypt(fn=PATH_CRYPT_KEYS, cell=self.cell) - return self._crypt_keys - - @property - def crypt_data(self): - if not hasattr(self,'_crypt_data'): - self._crypt_data = Crypt(fn=PATH_CRYPT_DATA, cell=self.cell) - return self._crypt_data - - def log(self,*x): - print(*x) - - def get_encypted_keys(self): + def op_keychain_encr(self): self.log('loading encrypted keys from disk') with open(PATH_OPERATOR_PUBKEY,'rb') as f_pub, open(PATH_OPERATOR_PRIVKEY,'rb') as f_priv: pubkey_encr = f_pub.read() privkey_encr = f_priv.read() - #self.log('loaded encrypted pubkey is:',pubkey_encr) - #self.log('loaded encrypted privkey is:',privkey_encr) + self.log('Operator pubkey_encr <--',pubkey_encr) + self.log('Operator privkey <--',privkey_encr) return (pubkey_encr,privkey_encr) - def get_keys(self): + def get_op_keys(self): + # Get passphrase + passphrase = 'aa' #@ HACK!!! + self.crypt_key,self. = self.get_k + + + # Do I have my keys? + have_keys = self.check_keys() + + pubkey_encr,privkey_encr = self.get_encypted_keys() # decrypt according to password of memory @@ -170,11 +163,40 @@ class TheOperator(object): return (pubkey_decr, privkey_decr, adminkey_decr) + # Magic key attributes + + + ## DECRYPTED REAL FINAL KEYS + + def pubkey(self, name, keychain_decr): + pubkey_decr = keychain_decr.get('pubkey_decr') + pubkey_encr = self.pubkey_encr(name) + if not pubkey_decr or not pubkey_encr: return None + pubkey = SCellSeal(key=pubkey_decr).decrypt(pubkey_encr) + return pubkey + + + def privkey(self, name, keychain_decr): + privkey_decr = keychain_decr.get('privkey_decr') + privkey_encr = self.privkey_encr(name, keychain_decr) + if not privkey_decr or not privkey_encr: return None + privkey = SCellSeal(key=privkey_decr).decrypt(privkey_encr) + return privkey + + def adminkey(self, name, keychain_decr): + adminkey_decr = keychain_decr.get('adminkey_decr') + adminkey_encr = self.adminkey_encr(name, keychain_decr) + if not adminkey_decr or not adminkey_encr: return None + adminkey = SCellSeal(key=adminkey_decr).decrypt(adminkey_encr) + return adminkey + + def exists(self,name): return self.crypt_keys.get(name,prefix='/pub_encr/') is not None - + def login(self, name, keychain_encr): + pass diff --git a/komrade/frontend/__init__.py b/komrade/frontend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frontend/app.json b/komrade/frontend/app.json similarity index 100% rename from frontend/app.json rename to komrade/frontend/app.json diff --git a/frontend/assets/Hammer_and_sickle.png b/komrade/frontend/assets/Hammer_and_sickle.png similarity index 100% rename from frontend/assets/Hammer_and_sickle.png rename to komrade/frontend/assets/Hammer_and_sickle.png diff --git a/frontend/assets/Hammer_and_sickle.xcf b/komrade/frontend/assets/Hammer_and_sickle.xcf similarity index 100% rename from frontend/assets/Hammer_and_sickle.xcf rename to komrade/frontend/assets/Hammer_and_sickle.xcf diff --git a/frontend/assets/Strengthen.ttf b/komrade/frontend/assets/Strengthen.ttf similarity index 100% rename from frontend/assets/Strengthen.ttf rename to komrade/frontend/assets/Strengthen.ttf diff --git a/frontend/assets/avatar.jpg b/komrade/frontend/assets/avatar.jpg similarity index 100% rename from frontend/assets/avatar.jpg rename to komrade/frontend/assets/avatar.jpg diff --git a/frontend/assets/avatars/elon.png b/komrade/frontend/assets/avatars/elon.png similarity index 100% rename from frontend/assets/avatars/elon.png rename to komrade/frontend/assets/avatars/elon.png diff --git a/frontend/assets/avatars/marx.png b/komrade/frontend/assets/avatars/marx.png similarity index 100% rename from frontend/assets/avatars/marx.png rename to komrade/frontend/assets/avatars/marx.png diff --git a/frontend/assets/avatars/zuck.png b/komrade/frontend/assets/avatars/zuck.png similarity index 100% rename from frontend/assets/avatars/zuck.png rename to komrade/frontend/assets/avatars/zuck.png diff --git a/frontend/assets/bg-brightblue.png b/komrade/frontend/assets/bg-brightblue.png similarity index 100% rename from frontend/assets/bg-brightblue.png rename to komrade/frontend/assets/bg-brightblue.png diff --git a/frontend/assets/bg-brown.png b/komrade/frontend/assets/bg-brown.png similarity index 100% rename from frontend/assets/bg-brown.png rename to komrade/frontend/assets/bg-brown.png diff --git a/frontend/assets/bg-green.png b/komrade/frontend/assets/bg-green.png similarity index 100% rename from frontend/assets/bg-green.png rename to komrade/frontend/assets/bg-green.png diff --git a/frontend/assets/bg-greenblue.png b/komrade/frontend/assets/bg-greenblue.png similarity index 100% rename from frontend/assets/bg-greenblue.png rename to komrade/frontend/assets/bg-greenblue.png diff --git a/frontend/assets/bg-purple.png b/komrade/frontend/assets/bg-purple.png similarity index 100% rename from frontend/assets/bg-purple.png rename to komrade/frontend/assets/bg-purple.png diff --git a/frontend/assets/bg-purple2.png b/komrade/frontend/assets/bg-purple2.png similarity index 100% rename from frontend/assets/bg-purple2.png rename to komrade/frontend/assets/bg-purple2.png diff --git a/frontend/assets/bg-russiangreen.png b/komrade/frontend/assets/bg-russiangreen.png similarity index 100% rename from frontend/assets/bg-russiangreen.png rename to komrade/frontend/assets/bg-russiangreen.png diff --git a/frontend/assets/bg.png b/komrade/frontend/assets/bg.png similarity index 100% rename from frontend/assets/bg.png rename to komrade/frontend/assets/bg.png diff --git a/frontend/assets/clenched-fist-vector-publicdomain.eps b/komrade/frontend/assets/clenched-fist-vector-publicdomain.eps similarity index 100% rename from frontend/assets/clenched-fist-vector-publicdomain.eps rename to komrade/frontend/assets/clenched-fist-vector-publicdomain.eps diff --git a/frontend/assets/clenched-fist-vector-publicdomain.xcf b/komrade/frontend/assets/clenched-fist-vector-publicdomain.xcf similarity index 100% rename from frontend/assets/clenched-fist-vector-publicdomain.xcf rename to komrade/frontend/assets/clenched-fist-vector-publicdomain.xcf diff --git a/frontend/assets/cover.jpg b/komrade/frontend/assets/cover.jpg similarity index 100% rename from frontend/assets/cover.jpg rename to komrade/frontend/assets/cover.jpg diff --git a/frontend/assets/fist.png b/komrade/frontend/assets/fist.png similarity index 100% rename from frontend/assets/fist.png rename to komrade/frontend/assets/fist.png diff --git a/frontend/assets/fist.xcf b/komrade/frontend/assets/fist.xcf similarity index 100% rename from frontend/assets/fist.xcf rename to komrade/frontend/assets/fist.xcf diff --git a/frontend/assets/fist2.png b/komrade/frontend/assets/fist2.png similarity index 100% rename from frontend/assets/fist2.png rename to komrade/frontend/assets/fist2.png diff --git a/frontend/assets/fist3.png b/komrade/frontend/assets/fist3.png similarity index 100% rename from frontend/assets/fist3.png rename to komrade/frontend/assets/fist3.png diff --git a/frontend/assets/font.otf b/komrade/frontend/assets/font.otf similarity index 100% rename from frontend/assets/font.otf rename to komrade/frontend/assets/font.otf diff --git a/frontend/assets/komrade-peek-2.gif b/komrade/frontend/assets/komrade-peek-2.gif similarity index 100% rename from frontend/assets/komrade-peek-2.gif rename to komrade/frontend/assets/komrade-peek-2.gif diff --git a/frontend/assets/komrade-screen-peek.gif b/komrade/frontend/assets/komrade-screen-peek.gif similarity index 100% rename from frontend/assets/komrade-screen-peek.gif rename to komrade/frontend/assets/komrade-screen-peek.gif diff --git a/frontend/assets/komrade-screen-preview-2020-08-23.gif b/komrade/frontend/assets/komrade-screen-preview-2020-08-23.gif similarity index 100% rename from frontend/assets/komrade-screen-preview-2020-08-23.gif rename to komrade/frontend/assets/komrade-screen-preview-2020-08-23.gif diff --git a/frontend/assets/komrade.png b/komrade/frontend/assets/komrade.png similarity index 100% rename from frontend/assets/komrade.png rename to komrade/frontend/assets/komrade.png diff --git a/frontend/assets/komrade2.png b/komrade/frontend/assets/komrade2.png similarity index 100% rename from frontend/assets/komrade2.png rename to komrade/frontend/assets/komrade2.png diff --git a/frontend/assets/komrade2.xcf b/komrade/frontend/assets/komrade2.xcf similarity index 100% rename from frontend/assets/komrade2.xcf rename to komrade/frontend/assets/komrade2.xcf diff --git a/frontend/assets/logo (copy).png b/komrade/frontend/assets/logo (copy).png similarity index 100% rename from frontend/assets/logo (copy).png rename to komrade/frontend/assets/logo (copy).png diff --git a/frontend/assets/logo.png b/komrade/frontend/assets/logo.png similarity index 100% rename from frontend/assets/logo.png rename to komrade/frontend/assets/logo.png diff --git a/frontend/assets/output.png b/komrade/frontend/assets/output.png similarity index 100% rename from frontend/assets/output.png rename to komrade/frontend/assets/output.png diff --git a/frontend/assets/overpass-mono-bold.otf b/komrade/frontend/assets/overpass-mono-bold.otf similarity index 100% rename from frontend/assets/overpass-mono-bold.otf rename to komrade/frontend/assets/overpass-mono-bold.otf diff --git a/frontend/assets/overpass-mono-light.otf b/komrade/frontend/assets/overpass-mono-light.otf similarity index 100% rename from frontend/assets/overpass-mono-light.otf rename to komrade/frontend/assets/overpass-mono-light.otf diff --git a/frontend/assets/overpass-mono-regular.otf b/komrade/frontend/assets/overpass-mono-regular.otf similarity index 100% rename from frontend/assets/overpass-mono-regular.otf rename to komrade/frontend/assets/overpass-mono-regular.otf diff --git a/frontend/assets/overpass-mono-semibold.otf b/komrade/frontend/assets/overpass-mono-semibold.otf similarity index 100% rename from frontend/assets/overpass-mono-semibold.otf rename to komrade/frontend/assets/overpass-mono-semibold.otf diff --git a/frontend/assets/screen-feed.png b/komrade/frontend/assets/screen-feed.png similarity index 100% rename from frontend/assets/screen-feed.png rename to komrade/frontend/assets/screen-feed.png diff --git a/frontend/assets/screen-login.png b/komrade/frontend/assets/screen-login.png similarity index 100% rename from frontend/assets/screen-login.png rename to komrade/frontend/assets/screen-login.png diff --git a/frontend/assets/screen-post.png b/komrade/frontend/assets/screen-post.png similarity index 100% rename from frontend/assets/screen-post.png rename to komrade/frontend/assets/screen-post.png diff --git a/frontend/assets/spiral2.png b/komrade/frontend/assets/spiral2.png similarity index 100% rename from frontend/assets/spiral2.png rename to komrade/frontend/assets/spiral2.png diff --git a/frontend/assets/spiral3.png b/komrade/frontend/assets/spiral3.png similarity index 100% rename from frontend/assets/spiral3.png rename to komrade/frontend/assets/spiral3.png diff --git a/frontend/assets/spiral3b.png b/komrade/frontend/assets/spiral3b.png similarity index 100% rename from frontend/assets/spiral3b.png rename to komrade/frontend/assets/spiral3b.png diff --git a/frontend/assets/spiral4.png b/komrade/frontend/assets/spiral4.png similarity index 100% rename from frontend/assets/spiral4.png rename to komrade/frontend/assets/spiral4.png diff --git a/frontend/assets/spiral4b.png b/komrade/frontend/assets/spiral4b.png similarity index 100% rename from frontend/assets/spiral4b.png rename to komrade/frontend/assets/spiral4b.png diff --git a/frontend/config.py b/komrade/frontend/config.py similarity index 100% rename from frontend/config.py rename to komrade/frontend/config.py diff --git a/frontend/etc/examples/kivy_asyncio_example.py b/komrade/frontend/etc/examples/kivy_asyncio_example.py similarity index 100% rename from frontend/etc/examples/kivy_asyncio_example.py rename to komrade/frontend/etc/examples/kivy_asyncio_example.py diff --git a/frontend/etc/first_node.py b/komrade/frontend/etc/first_node.py similarity index 100% rename from frontend/etc/first_node.py rename to komrade/frontend/etc/first_node.py diff --git a/frontend/etc/icon_preview.py b/komrade/frontend/etc/icon_preview.py similarity index 100% rename from frontend/etc/icon_preview.py rename to komrade/frontend/etc/icon_preview.py diff --git a/frontend/etc/test.py b/komrade/frontend/etc/test.py similarity index 100% rename from frontend/etc/test.py rename to komrade/frontend/etc/test.py diff --git a/frontend/main.py b/komrade/frontend/main.py similarity index 100% rename from frontend/main.py rename to komrade/frontend/main.py diff --git a/frontend/misc.py b/komrade/frontend/misc.py similarity index 100% rename from frontend/misc.py rename to komrade/frontend/misc.py diff --git a/frontend/pub b/komrade/frontend/pub similarity index 100% rename from frontend/pub rename to komrade/frontend/pub diff --git a/frontend/root.kv b/komrade/frontend/root.kv similarity index 100% rename from frontend/root.kv rename to komrade/frontend/root.kv diff --git a/frontend/run.sh b/komrade/frontend/run.sh similarity index 100% rename from frontend/run.sh rename to komrade/frontend/run.sh diff --git a/frontend/screens/base.py b/komrade/frontend/screens/base.py similarity index 100% rename from frontend/screens/base.py rename to komrade/frontend/screens/base.py diff --git a/frontend/screens/base.pyc b/komrade/frontend/screens/base.pyc similarity index 100% rename from frontend/screens/base.pyc rename to komrade/frontend/screens/base.pyc diff --git a/frontend/screens/feed/feed.kv b/komrade/frontend/screens/feed/feed.kv similarity index 100% rename from frontend/screens/feed/feed.kv rename to komrade/frontend/screens/feed/feed.kv diff --git a/frontend/screens/feed/feed.py b/komrade/frontend/screens/feed/feed.py similarity index 100% rename from frontend/screens/feed/feed.py rename to komrade/frontend/screens/feed/feed.py diff --git a/frontend/screens/login/login.kv b/komrade/frontend/screens/login/login.kv similarity index 100% rename from frontend/screens/login/login.kv rename to komrade/frontend/screens/login/login.kv diff --git a/frontend/screens/login/login.py b/komrade/frontend/screens/login/login.py similarity index 100% rename from frontend/screens/login/login.py rename to komrade/frontend/screens/login/login.py diff --git a/frontend/screens/messages/messages.kv b/komrade/frontend/screens/messages/messages.kv similarity index 100% rename from frontend/screens/messages/messages.kv rename to komrade/frontend/screens/messages/messages.kv diff --git a/frontend/screens/messages/messages.py b/komrade/frontend/screens/messages/messages.py similarity index 100% rename from frontend/screens/messages/messages.py rename to komrade/frontend/screens/messages/messages.py diff --git a/frontend/screens/notifications/notifications.kv b/komrade/frontend/screens/notifications/notifications.kv similarity index 100% rename from frontend/screens/notifications/notifications.kv rename to komrade/frontend/screens/notifications/notifications.kv diff --git a/frontend/screens/notifications/notifications.py b/komrade/frontend/screens/notifications/notifications.py similarity index 100% rename from frontend/screens/notifications/notifications.py rename to komrade/frontend/screens/notifications/notifications.py diff --git a/frontend/screens/post/post.kv b/komrade/frontend/screens/post/post.kv similarity index 100% rename from frontend/screens/post/post.kv rename to komrade/frontend/screens/post/post.kv diff --git a/frontend/screens/post/post.py b/komrade/frontend/screens/post/post.py similarity index 100% rename from frontend/screens/post/post.py rename to komrade/frontend/screens/post/post.py diff --git a/frontend/screens/profile/profile.kv b/komrade/frontend/screens/profile/profile.kv similarity index 100% rename from frontend/screens/profile/profile.kv rename to komrade/frontend/screens/profile/profile.kv diff --git a/frontend/screens/profile/profile.py b/komrade/frontend/screens/profile/profile.py similarity index 100% rename from frontend/screens/profile/profile.py rename to komrade/frontend/screens/profile/profile.py diff --git a/frontend/watcher.py b/komrade/frontend/watcher.py similarity index 100% rename from frontend/watcher.py rename to komrade/frontend/watcher.py