You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Comrad/p2p/persona.py

696 lines
23 KiB
Python

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 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 = '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._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'
self.log('Q:',Q)
r = requests.get(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 = requests.get(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 login_or_register(self):
keyserver_pubkey_b64 = self.get_keyserver_pubkey()
keyserver_pubkey = b64decode(keyserver_pubkey_b64)
self.log('keyserver_pubkey =',keyserver_pubkey)
if keyserver_pubkey is None: return {'error':'Cannot conntact keyserver'}
pubkey_ext_b64, signed_pubkey_ext_b64, server_signed_pubkey_ext_b64 = self.get_externally_signed_pubkey()
self.log('pubkey_ext_b64 =',pubkey_ext_b64)
self.log('signed_pubkey_ext_b64 =',signed_pubkey_ext_b64)
self.log('server_signed_pubkey_ext_b64 =',server_signed_pubkey_ext_b64)
# does not exist
if pubkey_ext_b64 is b'' or signed_pubkey_ext_b64 is b'' or server_signed_pubkey_ext_b64 is b'':
# 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'}
else:
# check keyserver is telling truth
keyserver_verified = self.verify(server_signed_pubkey_ext_b64, keyserver_pubkey_b64)
if keyserver_verified is None:
return {'error':'Keyserver verification failed'}
# do I have local copies?
self.find_keys_local()
self.log('self.pubkey_b64 on disk is',self.pubkey_b64)
self.log('pubkey_ext_b64 on server is',pubkey_ext_b64)
self.log('signed_pubkey_ext_b64 =',signed_pubkey_ext_b64)
# self.log('self.signed_pubkey_b64',self.signed_pubkey_b64)
self.log('keyserver_verified',keyserver_verified)
#pubkeys_match = self.verify(signed_pubkey_ext_b64, self.pubkey_b64)
# pubkeys_match = (self.pubkey_b64 == pubkey_ext_b64) and (self.pubkey_b64 == keyserver_verified)
# pubkey_ext = b64decode(pubkey_ext_b64)
pubkey_b64 = b64encode(self.pubkey)
# self.log('pubkey_ext =',pubkey_ext)
# me_verified = self.verify(signed_pubkey_ext_b64, pubkey_b64)
# self.log('me_verified =',me_verified)
me_verified = True
# pubkeys_match = pubkey_b64 == pubkey_ext_b64
pubkeys_match = True
self.log('pubkeys_match',pubkeys_match)
ok_to_load_as_acquaintance = bool(keyserver_verified)
enc_match = False
if os.path.exists(self.key_path_pub_enc):
with open(self.key_path_pub_enc,'rb') as f:
key_pub_enc=f.read()
self.log('key_pub_enc =',key_pub_enc)
decr_pub_key = self.decrypt(key_pub_enc, KOMRADE_PUB_KEY, self.privkey_b64)
self.log('decr_pub_key',decr_pub_key)
self.log('keyserver_verified',keyserver_verified)
enc_match = decr_pub_key == keyserver_verified
ok_to_login = enc_match
# self.log('my_server_signed_pubkey_b64 on disk is',my_server_signed_pubkey_b64)
# self.log('server_signed_pubkey_ext_b64 on server is',server_signed_pubkey_ext_b64)
if ok_to_login:
# I CLAIM TO *BE* THIS PERSON
return {'success':'Logging back in'}
# elif ok_to_load_as_contact:
# return {'success':'Loaded as contact'}
# elif ok_to_load_as_acquaintance:
else:
# just meeting this person as a contact
self.pubkey=pubkey_ext
self.log('setting self.pubkey to external value:',self.pubkey)
return {'success':'Met person as acquaintance'}
async def boot(self):
res = self.login_or_register()
self.log('boot -->',res)
return res
async def boot1(self):
self.log(f'>> Persona.boot()')
# self.load_or_gen()
await self.find_keys()
if self.pubkey is None and self.create_if_missing:
self.gen_keys()
await self.set_pubkey_p2p()
if self.privkey and self.pubkey:
return {'success':'Logged in...'}
return {'error':'Login failed'}
@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.enc')
@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)
# @property
# def encrypted_pubkey_b64(self):
# self.log('encrypting!',self.pubkey_b64, KOMRADE_PRIV_KEY)
# res = self.encrypt(self.pubkey_b64, self.pubkey_b64, self.app_person.privkey_b64)
# self.log('RES!?',res)
# return res
## genearating keys
def gen_keys(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)
## 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())
# if os.path.exists(self.key_path_pub):
# with open(self.key_path_pub) as pub_f:
# self.pubkey=b64decode(pub_f.read())
if os.path.exists(self.key_path_pub_enc) and self.privkey:
with open(self.key_path_pub_enc) as pub_f:
self.pubkey=self.decrypt(pub_f.read(), KOMRADE_PUB_KEY)
self.log('loaded self.pubkey',self.pubkey)
## finding on p2p net
async def find_keys_p2p(self,name=None):
await self.node
if not name: name=self.name
self.log(f'find_keys_p2p(name={name})')
name_b64=b64encode(name.encode())
node = await self.node
key=P2P_PREFIX+name.encode()
self.log(f'LOOKING FOR: {key}')
res = await node.get(key)
self.log(f'FOUND: {res}')
if res is not None:
signed_msg,pubkey_b64 = b64decode(res).split(BSEP)
self.log('!!!!!',signed_msg,pubkey_b64)
verified_msg = self.verify(signed_msg,pubkey_b64)
if verified_msg is not None:
self.log('verified message!',verified_msg)
name,pubkey_b64 = verified_msg.split(BSEP2)
self.pubkey = b64decode(pubkey_b64)
self.name = b64decode(name).decode()
print('>> found pubkey!',self.pubkey_b64,'and name',self.name)
## save back to cache?
self.save_pubkey(pubkey_b64)
async def find_keys(self):
#self.find_keys_local() # <- now doing this, which is not async, in __init__()
if self.pubkey is None:
await self.find_keys_p2p()
# if self.pubkey is not None:
# # save back to local cache?
def save_pubkey(self,pubkey_b64):
with open(self.key_path_pub, "wb") as public_key_file:
public_key_file.write(pubkey_b64)
self.log('>> saved:',self.key_path_pub)
# async def set_pubkey_p2p(self):
# # signed_name = self.sign(b64encode(self.name.encode()))
# # signed_pubkey_b64 = self.sign(self.pubkey_b64)
# self.log(f'set_pubkey_p2p()...')
# signed_msg = self.sign(self.name_b64 + BSEP2 + self.pubkey_b64)
# package = b64encode(signed_msg + BSEP +self.pubkey_b64)
# #await self.api.set('/pubkey/'+self.name,package,encode_data=False)
# key=P2P_PREFIX+self.name.encode()
# newval=package
# self.log('set_pubkey()',key,'-->',newval)
# node = await self.node
# return await node.set(key,newval)
## 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
## EVEN HIGHER LEVEL STUFF: person 2 person
@property
async def world(self): return self.api.keys['world']
@property
async def komrade(self): return self.api.keys['komrade']
async def send(self,msg_b,to):
return await self.api.send(msg_b,self,to)
@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_person(self):
return self.api.keys['komrade']
@property
def app_pubkey_b64(self):
return self.app_person.pubkey_b64
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,self.app_person.pubkey_b64) 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.app_person.encrypt(msg_uri, self.pubkey_b64)
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
komrade = await self.komrade
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.pubkey_b64)
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()
# komrade = Persona('komrade')
# await komrade.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())