import os,sys; sys.path.append(os.path.abspath(os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__),'..')),'..'))) from komrade import * from komrade.backend.crypt import * from abc import ABC, abstractmethod # common external imports 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 class KomradeKey(ABC,Logger): @abstractmethod def encrypt(self,msg,**kwargs): pass @abstractmethod def decrypt(self,msg,**kwargs): pass @abstractmethod def data(self): pass @property def data_b64(self):return b64encode(self.data) @property def discreet(self): return make_key_discreet(self.data) def __str__(self): return repr(self) class KomradeSymmetricKey(KomradeKey): @property def cell(self): if not hasattr(self,'_cell'): if hasattr(self,'passphrase') and self.passphrase: self._cell = SCellSeal(passphrase=self.passphrase) elif hasattr(self,'key') and self.key: self._cell = SCellSeal(key=self.key) return self._cell def encrypt(self,msg,**kwargs): if issubclass(type(msg), KomradeKey): msg=msg.data return self.cell.encrypt(msg,**kwargs) def decrypt(self,msg,**kwargs): return self.cell.decrypt(msg,**kwargs) def getpass_status(passphrase=None): while not passphrase: passphrase1 = getpass(f'@Keymaker: What is a *memorable* pass word or phrase? Do not write it down.\n@{name}: ') passphrase2 = getpass(f'@Keymaker: Could you repeat that?') if passphrase1!=passphrase2: self.status('@Keymaker: Those passwords didn\'t match. Please try again.',clear=False,pause=False) else: return passphrase1 # get_pass_func = getpass_status if SHOW_STATUS else getpass from getpass import getpass class KomradeSymmetricKeyWithPassphrase(KomradeSymmetricKey): def __init__(self,passphrase=DEBUG_DEFAULT_PASSPHRASE, why=WHY_MSG): self.passphrase=passphrase if not self.passphrase: self.passphrase=getpass(why) #return self.passphrase @property def data(self): return KEY_TYPE_SYMMETRIC_WITH_PASSPHRASE.encode('utf-8') def __repr__(self): return f'[Symmetric Key] (generated by password)' class KomradeSymmetricKeyWithoutPassphrase(KomradeSymmetricKey): def __init__(self,key=None): self.key = GenerateSymmetricKey() if not key else key @property def data(self): return self.key def __repr__(self): return f'[Symmetric Key]\n ({self.discreet})' class KomradeAsymmetricKey(KomradeKey): def __init__(self,pubkey=None,privkey=None): if not pubkey or not privkey: keypair = GenerateKeyPair(KEY_PAIR_TYPE.EC) privkey = keypair.export_private_key() pubkey = keypair.export_public_key() self.pubkey=pubkey self.privkey=privkey self.privkey_obj = KomradeAsymmetricPrivateKey(privkey,pubkey) self.pubkey_obj = KomradeAsymmetricPublicKey(pubkey,privkey) def encrypt(self,msg,pubkey=None,privkey=None): if issubclass(type(msg), KomradeKey): msg=msg.data pubkey=pubkey if pubkey else self.pubkey privkey=privkey if privkey else self.privkey return SMessage(privkey,pubkey).wrap(msg) def decrypt(self,msg,pubkey=None,privkey=None): pubkey=pubkey if pubkey else self.pubkey privkey=privkey if privkey else self.privkey return SMessage(privkey,pubkey).unwrap(msg) @property def data(self): return self.key class KomradeAsymmetricPublicKey(KomradeAsymmetricKey): def __init__(self,pubkey,privkey=None): self.pubkey=pubkey self.privkey=privkey @property def key(self): return self.pubkey @property def data(self): return self.pubkey def __repr__(self): return f'''[Asymmetric Public Key]\n ({self.data_b64.decode()})''' class KomradeAsymmetricPrivateKey(KomradeAsymmetricKey): def __init__(self,privkey,pubkey=None): self.pubkey=pubkey self.privkey=privkey @property def data(self): return self.privkey @property def key(self): return self.privkey def __repr__(self): return f'''[Asymmetric Private Key]\n ({self.discreet})''' def make_key_discreet(data,chance_unredacted=0.333): import random if not data: return '?' if not isBase64(data): data=b64encode(data) key=data.decode() return ''.join((k if random.random()\n{_key_encr}') # keychain[key_name]=_key_encr keychain[key_name]=_key_encr_obj return keychain def forge_new_keys(self, name=None, passphrase=DEBUG_DEFAULT_PASSPHRASE, keys_to_save = KEYMAKER_DEFAULT_KEYS_TO_SAVE_ON_SERVER, keys_to_return = KEYMAKER_DEFAULT_KEYS_TO_SAVE_ON_CLIENT, keys_to_gen = KEYMAKER_DEFAULT_KEYS_TO_GEN, key_types = KEYMAKER_DEFAULT_KEY_TYPES, save_keychain=True, return_keychain=True, return_all_keys=False): # setup keys_to_gen = set(keys_to_gen) | set(keys_to_save) | set(keys_to_return) keys_to_gen = sorted(list(keys_to_gen),key=lambda x: x.count('_')) key_types = dict([(k,key_types[k]) for k in keys_to_gen]) if not name: name=self.name # show user what's happening self.log(f''' Keymaker ({self}) is forging new keys for {name} ''' + (''' * I will save these keys in this crypt: {', '.join(keys_to_save)} ''' if save_keychain else '') #+ #''' # * I will also save this user's pubkey (as b64 URI) to: # {self.get_path_qrcode(name=name)} # ''' + (f''' + (f''' * I will return these keys to you: {', '.join(keys_to_return)} ''' if return_keychain else '') + f''' * I will forge these keys for you: {', '.join(keys_to_gen)} * I will be using these key types to do so: {dict_format(key_types,tab=4)} ''') # gen decryptor keys! keychain = self.gen_keys_from_types(key_types,passphrase=passphrase) # gen encrypted keys! # self.log('I built this keychain v1!',dict_format(keychain,tab=2)) keychain = self.gen_encr_keys(keychain,keys_to_gen,passphrase=passphrase) # self.log('I built this keychain!',dict_format(keychain,tab=2)) # self.status('@Keymaker: I ended up building these keys:',keychain) # save keys! if save_keychain: # get URI id to save under (except for pubkeys, accessible by name) uri_id,keys_saved_d,keychain = self.save_keychain(name,keychain,keys_to_save) # self.log('I saved this keychain:',dict_format(keys_saved_d,tab=2),'using the generated-from-pubkey URI ID',uri_id) # return keys! if return_all_keys: return keychain if return_keychain: keys_returned = self.return_keychain(keychain,keys_to_return) # self.log('I am returning this keychain:',dict_format(keys_returned,tab=2)) # return (uri_id,keys_returned) return keys_returned raise KomradeException('What did you want me to do here?') def return_keychain(self,keychain,keys_to_return=None): keychain_toreturn = {} if not keys_to_return: keys_to_return = list(keychain.keys()) for key in keys_to_return: if key in keychain: keychain_toreturn[key]=keychain[key] return keychain_toreturn def get_path_qrcode(self,name=None,dir=None,ext='.png'): if not name: name=self.name if not dir: dir = PATH_QRCODES fnfn = os.path.join(dir,name+ext) return fnfn @property def qr(self): return self.qr_str(data=self.uri_id) def qr_str(self,data=None): data = self.uri_id if not data else data return get_qr_str(data) def save_uri_as_qrcode(self,uri_id=None,name=None): if not uri_id: uri_id = self.uri_id if not uri_id and not self.uri_id: raise KomradeException('Need URI id to save!') if not name: name=self.name # gen import pyqrcode qr = pyqrcode.create(uri_id) ofnfn = self.get_path_qrcode(name=name) qr.png(ofnfn,scale=5) self._uri_id = uri_id self.log(f'''Saved URI(=pubkey_b64) as a QR code: {ofnfn} {self.qr}''') def save_keychain(self,name,keychain,keys_to_save=None,uri_id=None): if not keys_to_save: keys_to_save = list(keychain.keys()) if not uri_id and 'pubkey' in keychain: uri_id = b64encode(keychain['pubkey'].data).decode() #uri_id = get_random_id() + get_random_id() # self.log(f'SAVING KEYCHAIN FOR {name} under URI {uri_id}') self._uri_id = uri_id # filter for transfer for k,v in keychain.items(): if issubclass(type(v),KomradeKey): v=v.data keychain[k]=v # save keychain keys_saved_d={} for keyname in keys_to_save: if not '_' in keyname and keyname!='pubkey': self.log('there is no private property in a socialist network! all keys must be split between komrades',keyname) if keyname in keychain: # uri = uri_id uri = uri_id if keyname!='pubkey' else name if not uri: raise KomradeException('invalid URI! {uri}') val = keychain[keyname] if issubclass(type(keychain[keyname]), KomradeKey) or issubclass(type(keychain[keyname]), KomradeEncryptedKey): val = val.data self.crypt_keys.set(uri,val,prefix=f'/{keyname}/') keys_saved_d[keyname] = keychain[keyname] # save pubkey as QR if not 'pubkey' in keys_saved_d: # self.log('did not save pubkey in crypt, storing as QR...') self.save_uri_as_qrcode(name=name, uri_id=uri_id) # set to my keychain right away self._keychain = keychain return (uri_id,keys_saved_d,keychain) def assemble(self,_keychain): # last minute assemblies? encr_keys = [k for k in _keychain if k.endswith('_encr')] for ekey in encr_keys: eval=_keychain[ekey] if not eval: continue unencrkey = ekey[:-len('_encr')] if unencrkey in _keychain: continue decrkey = unencrkey+'_decr' if decrkey not in _keychain: continue dval=_keychain[decrkey] if not dval: continue # self.log(ekey,decrkey,'??') # self.log(eval,dval,'????') new_val = self.assemble_key(eval,dval) # self.log('!!#!',new_val) if new_val: _keychain[unencrkey] = new_val return _keychain def assemble_key(self, key_encr, key_decr, key_encr_name=None, key_decr_name=None): # self.log(f'assembling key: {key_decr} decrypting {key_encr}') # need the encrypted half if not key_encr: # self.log('!! encrypted half not given') return if not key_decr: if self.passphrase: key_decr = self.passphrase else: # self.log('!! decryptor half not given') return # need some way to regenerate the decryptor decr_cell = self.get_cell(key_decr) # need the decryptor half if not decr_cell: # self.log('!! decryptor cell not regenerable') return # decrypt! try: # self.log(f'>> decrypting {key_encr_name} with {key_decr_name}\n({key_encr} with cell {decr_cell}') key = decr_cell.decrypt(key_encr) # self.log('assembled_key built:',key) return key except ThemisError as e: self.log('!! decryption failed:',e) return def get_cell(self, str_or_key_or_cell): # self.log('getting decr cell for',str_or_key_or_cell) if type(str_or_key_or_cell)==SCellSeal: return str_or_key_or_cell elif type(str_or_key_or_cell)==str: return SCellSeal(passphrase=str_or_key_or_cell) elif type(str_or_key_or_cell)==bytes: return SCellSeal(key=str_or_key_or_cell) elif issubclass(type(str_or_key_or_cell),KomradeSymmetricKey): return str_or_key_or_cell.cell if __name__ == '__main__': keymaker = Keymaker('marx69') keychain = keymaker.forge_new_keys() print(keychain)