From 72d6821556549f89acccf9b2c9e3ae8119a024ec Mon Sep 17 00:00:00 2001 From: quadrismegistus Date: Sat, 12 Sep 2020 08:55:23 +0100 Subject: [PATCH] updates --- komrade/backend/keymaker.py | 28 +- komrade/backend/people.py | 43 +- komrade/cli/artcode.py | 269 +++++++---- komrade/cli/artcode0.py | 920 ++++++++++++++++++++++++++++++++++++ komrade/cli/cli.py | 249 ++++++---- komrade/cli/cli0.py | 415 ++++++++++++++++ komrade/cli/cli_curses.py | 483 +++++++++++++++++++ komrade/constants.py | 5 +- komrade/utils.py | 15 +- 9 files changed, 2218 insertions(+), 209 deletions(-) create mode 100644 komrade/cli/artcode0.py create mode 100644 komrade/cli/cli0.py create mode 100644 komrade/cli/cli_curses.py diff --git a/komrade/backend/keymaker.py b/komrade/backend/keymaker.py index bab9c34..7d718be 100644 --- a/komrade/backend/keymaker.py +++ b/komrade/backend/keymaker.py @@ -52,8 +52,8 @@ def getpass_status(passphrase=None): else: return passphrase1 -get_pass_func = getpass_status if SHOW_STATUS else getpass - +# 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): @@ -63,14 +63,14 @@ class KomradeSymmetricKeyWithPassphrase(KomradeSymmetricKey): #return self.passphrase @property def data(self): return KEY_TYPE_SYMMETRIC_WITH_PASSPHRASE.encode('utf-8') - def __repr__(self): return f'[Symmetric Key] ({self.discreet})' + 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] ({self.discreet})' + def __repr__(self): return f'[Symmetric Key]\n ({self.discreet})' @@ -106,7 +106,7 @@ class KomradeAsymmetricPublicKey(KomradeAsymmetricKey): @property def data(self): return self.pubkey - def __repr__(self): return f'''[Asymmetric Public Key] ({self.data_b64.decode()})''' + 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 @@ -115,21 +115,21 @@ class KomradeAsymmetricPrivateKey(KomradeAsymmetricKey): def data(self): return self.privkey @property def key(self): return self.privkey - def __repr__(self): return f'''[Asymmetric Private Key] ({self.discreet})''' + def __repr__(self): return f'''[Asymmetric Private Key]\n ({self.discreet})''' -def make_key_discreet(data,chance_redacted=0.5): +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():===========` + )( + "" +""" + + + +ART_KEY_PAIR = """ + + __ + /o \\_____ + \__/-="="` + __ + / o\\ + \_ / + <| + <| + <| + ` +""" + + +ART_KEY_PAIR2 = """ + + __ + /o \\_____ + \__/-="="` + __ (public) + / o\\ + \_ / + <| + <| + <| (private) + +""" + + +ART_KEY_PAIR2A = """ + + __ + /o \\_____ + \__/-="="` + __ (1) public key + / o\\ + \_ / + <| + <| + <| + +""" + + +ART_KEY_PAIR2B = """ + + __ + /o \\_____ + \__/-="="` + __ (1) public key + / o\\ + \_ / + <| + <| + <| (2) private key + +""" + + +ART_KEY_PAIR_SPLITTING1 = """ + _ + /o + \ + < + < + < + + (2A) + symmetric + encryption + key, from + hashed + passphrase + +""" + + +ART_KEY_PAIR_SPLITTING2 = """ + _ __ + /o / o\\ + \ \_ / + < --(encrypts)--> <| + < <| + < <| + + (2A) (2) + symmetric asymmetric + encryption private + key, from key + hashed + passphrase + +""" + +ART_KEY_PAIR_SPLITTING3 = """ + _ __ _ + /o / o\\ \\ + \ \_ / / + < --(encrypts)--> <| --(into)--> | + < <| | + < <| | + + (2A) (2) (2B) + symmetric asymmetric encrypted form + encryption private of (2) + key, from key + hashed + passphrase + +""" + + +ART_KEY_PAIR_SPLITTING4 = """ + _ _ + /o \\ + \ / + < | + < | + < | + + (2A) (2B) + symmetric encrypted form + encryption of (2) + key, from + hashed + passphrase + +""" + + + + +ART_KEY_PAIR_SPLITTING5 = """ + _ + \\ + / + | + | + | + + (2B) + encrypted form + of (2) + + + + +""" + + + + + + +ART_KEY_PAIR32 = """ + + __ + /o \\_____ + \__/-="="` + _ _ (1) public key + /o \\ + \ _ / + < | + < | + < | (2B) privkey_encr + + (2A) privkey_decr + +""" + + +ART_KEY_PAIR3A2 = """ + + __ + /o \\_____ + \__/-="="` + _ _ (1) public key + /o \\ + \ _ / + < | + < | + < | + + + +""" + +ART_KEY_KEY2A = """ _ + /o + \ + < + < + < + +""" + +ART_KEY_PAIR3B2 = """ + + __ + /o \\_____ + \__/-="="` + _ _ + /o \\ + \ _ / + < | + < | + < | (2B) privkey_encr + + + +""" + + +ART_KEY_PAIR3C2 = """ + + __ + /o \\_____ + \__/-="="` + _ _ (1) public key + /o \\ + \ _ / + < | + < | + < | (2B) privkey_encr + + (2A) privkey_decr + +""" + + +ART_KEY_PAIR3 = """ + + __ + /o \\_____ + \__/-="="` + _ _ (1) public key + /o \\ + \ _ / + < | + < | + < | (2B) privkey_encr + + (2A) privkey_decr + +""" + +ART_KEY_PAIR4 = """ + + __ + /o \\_____ + \__/-="="` + _ + \\ + _ / + | + | + | (2B) privkey_encr + + (2A) privkey_decr + +""" + +ART_KEY_PAIR4Z = """ + + __ + /o \\_____ + \__/-="="` + _ + \\ + _ / + | + | + | + + (2A) privkey_decr + +""" + + +ART_KEY_PAIR4D = """ + _ + \\ + _ / + | + | + | (2B) privkey_encr + + (2A) privkey_decr + +""" + +ART_KEY_PAIR4C = """ + _ + \\ + _ / + | + | + | + + (2A) privkey_decr + +""" + + +ART_KEY_PAIR4B = """ _ + \\ + _ / + | + | + | + +""" + +ART_KEY_PAIR31A = """ + + __ + /o \\_____ + \__/-="="` + (1) public key""" + + +ART_KEY_PAIR3A = """ + + __ + /o \\_____ + \__/-="="`""" +ART_KEY_PAIR3B = """ _ _ + /o \\ + \ _ / + < | + < | + < | +(2A) (2B) encrypted + encryption form of (2) + key for (2B) + +""" + +ART_KEY_PAIR5 = """ + + __ + /o \\_____ + \__/-="="` + (1) public key + _ _ + /o \\ + \ _ / + < | + < | + < | +(2A) (2B) encrypted + encryption form of (2) + key for (2B) +""" + + +ART_KEY_PAIR4Z1 = """ + + __ + /o \\_____ + \__/-="="` + (1) public key + _ + \\ + _ / + | + | + | + (2B) encrypted + form of (2) + +""" +ART_KEY_PAIR4ZZ = """ + + __ + /o \\_____ + \__/-="="` + + _ + \\ + _ / + | + | + | + + + +""" + + +ART_KEY_PAIR4Z2 = """ + + __ + /o \\_____ + \__/-="="` + (1) public key + + + + + + + + + +""" +# """ +ART_KEY_PAIR4Z3 = """ + + __ + /o \\_____ + \__/-="="` + (1) public key + + + + + + + + +""" +ART_KEY_PAIR4Z3 = """ + + __ + /o \\_____ + \__/-="="` + (1) public key + + + + + + + + +""" + +ART_KEY_PAIR4Z42 = """ + + + + + + + + + + + + + +""" + + +ART_KEY_PAIR4Z4B = """ + + + + + __ + / o\\ + \_ / + <| + <| + <| + + + +""" + +ART_KEY_PAIR4Z4 = """ + + + + + __ + / o\\ + \_ / + <| + <| + <| + + + +""" + + +ART_KEY_PAIR3BB = """ _ + \\ + _ / + | + | + | +(2A) (2B) encrypted + encryption form of (2) + key for (2B) + +""" + + + + +ART_KEY_PAIR3AA = """ _ _ + /o \\ + \ _ / + < | + < | + < | + +""" + + + +ART_KEY_PAIR_SEP = """ + + __ + /o \\_____ + \__/-="="` + __ + / o\\ + \_ / + <| + <| + <| + ` +""" + + + +ART_KEY_CHAIN = """ + ___________ @ @ + / (@\\ @ + \___________/ _@ + @ _/@ \\_____ + @/ \__/-="="` + \_ / + <| + <| + <| + ` +""" + + +ART_FROG_BLENDER =""" + ___ + _______|___|______ +__|__________________| +\ ]________________[ `---. + `. ___ L + | _ | L | + | .'_`--.___ __ | | | + |( 'o` - .`.'_ ) | F F + | `-._ `_`./_ | / / + J '/\\ ( .'/ )F.' / + L ,__//`---'`-'_/J .' + J /-' '/ F.' + L ' J' + J `.`-. .-'.' F + L `.-'.-' J + |__(__(___)__| + F J + J L + |______________| + +""" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#### +# code +### +from PIL import Image +ASCII_CHARS = [ '#', ' ', '%', '.', 'S', '+', '.', '*', ':', ',', '@'] + +def scale_image(image, new_width=100): + """Resizes an image preserving the aspect ratio. + """ + (original_width, original_height) = image.size + aspect_ratio = original_height/float(original_width) + new_height = int(aspect_ratio * new_width) + + new_image = image.resize((new_width, new_height)) + return new_image + +def convert_to_grayscale(image): + return image.convert('L') + +def map_pixels_to_ascii_chars(image, range_width=25): + """Maps each pixel to an ascii char based on the range + in which it lies. + + 0-255 is divided into 11 ranges of 25 pixels each. + """ + + pixels_in_image = list(image.getdata()) + pixels_to_chars = [ASCII_CHARS[pixel_value//range_width] for pixel_value in + pixels_in_image] + + return "".join(pixels_to_chars) + +def convert_image_to_ascii(image, new_width=100): + image = scale_image(image) + image = convert_to_grayscale(image) + + pixels_to_chars = map_pixels_to_ascii_chars(image) + len_pixels_to_chars = len(pixels_to_chars) + + image_ascii = [pixels_to_chars[index: index + new_width] for index in + range(0, len_pixels_to_chars, new_width)] + + return "\\n".join(image_ascii) + +def handle_image_conversion(image_filepath): + image = None + try: + image = Image.open(image_filepath) + except Exception as e: + # print "Unable to open image file {image_filepath}.".format(image_filepath=image_filepath) + # print e + return + + image_ascii = convert_image_to_ascii(image) + print(image_ascii) + +if __name__=='__main__': + import sys + + image_file_path = sys.argv[1] + handle_image_conversion(image_file_path) \ No newline at end of file diff --git a/komrade/cli/cli.py b/komrade/cli/cli.py index d215d05..1f538d9 100644 --- a/komrade/cli/cli.py +++ b/komrade/cli/cli.py @@ -13,9 +13,10 @@ class CLI(Logger): 'login':'log back in' } - def __init__(self): - self.name='' - self.cmd='' + def __init__(self,name='',cmd='',persona=None): + self.name=name + self.cmd=cmd + self.persona=persona def run(self,inp='',name=''): self.name=name @@ -89,7 +90,7 @@ class CLI(Logger): None,{ART_OLDPHONE4},f'''@{name}: Hm, ok. Well, could you patch me through to the remote operator then?''', - f'''@{TELEPHONE_NAME}: I could, but it's not safe yet. Your information could be exposed. You need to forge your encryption keys first.''', + f'''@{TELEPHONE_NAME}: I could, but it's not safe yet. Your information could be exposed. You need to cut your encryption keys first.''', f'@{name}: Fine, but how do I do that?', @@ -106,20 +107,8 @@ class CLI(Logger): f'@Keymaker: Of course, Komrade @{name}.', ) - return name - - - - def status_keymaker_part2(self,name,passphrase,pubkey,privkey,hasher,persona): - from getpass import getpass - # gen what we need - uri_id = pubkey.data_b64 - qr_str = get_qr_str(uri_id) - qr_path = os.path.join(PATH_QRCODES,name+'.png') - - # what are pub/priv? self.status( - 'I will forge for you two matching keys, part of an "asymmetric" pair.', + 'I will cut for you two matching keys, part of an "asymmetric" pair.', 'Please, watch me work.', None,{tw.indent(ART_KEY,' '*5)+'\n'}, @@ -133,58 +122,55 @@ class CLI(Logger): None, {ART_KEY_PAIR,True} ) #,clear=False,indent=10,pause=False) - self.status( - None,{ART_KEY_PAIR}, - 'A matching set of keys have been generated.', - None,{ART_KEY_PAIR2A+'\n\nA matching set of keys have been generated.'}, - '1) First, I have made a "public key" which you can share with anyone:', - f'{repr(pubkey)}', - 'This key is a randomly-generated binary string, which acts as your "address" on Komrade.', - 'By sharing this key with someone, you enable them to write you an encrypted message which only you can read.' - ) - self.status( - None,{ART_KEY_PAIR2A}, - f'You can share your public key by copy/pasting it to them over a secure channel (e.g. Signal).', - 'Or, you can share it as a QR code, especially phone to phone:', - {qr_str+'\n\n',True,5}, - f'\n\n(If registration is successful, this QR code be saved as an image to your device at: {qr_path}.)' - ) + + return name + + + + def status_keymaker_part2(self,name,passphrase,pubkey,privkey,hasher,persona): + from getpass import getpass + # gen what we need + uri_id = pubkey.data_b64 + qr_str = get_qr_str(uri_id) + qr_path = os.path.join(PATH_QRCODES,name+'.png') + + # what are pub/priv? + + # self.status( + # None,{ART_KEY_PAIR}, + # 'A matching set of keys have been generated.', + # None,{ART_KEY_PAIR2A+'\nA matching set of keys have been generated.'+'\n'}, + # 'First, I have made a "public key" which you can share with anyone:', + # f'(1) {pubkey.data_b64.decode()}', + # 'This key is a randomly-generated binary string, which acts as your "address" on Komrade.', + # 'With it, someone can write you an encrypted message which only you can read.' + # ) + # self.status( + # None,{ART_KEY_PAIR2A}, + # f'You can share your public key by copy/pasting it to them over a secure channel (e.g. Signal).', + # 'Or, you can share it as a QR code, especially IRL:', + # {qr_str+'\n\n',True,5}, + # f'\n\n(If registration is successful, this QR code be saved as an image to your device at: {qr_path}.)' + # ) # private keys self.status(None, {ART_KEY_PAIR2B}, - 'Second, I have forged a matching "private key":', - f'{repr(privkey)}', - 'With it, you can decrypt any message sent to you via your public key.', - 'You you should never, ever give this key to anyone.', - 'In fact, this key is so dangerous that I will immediately destroy it by splitting it into two half-keys:' + 'Second, I have cut a matching "private key".', + "It's too dangerous to show in full, so here it is 66% redacted:", + f'(2) {make_key_discreet(privkey.data_b64,0.3)}', + 'With it, you can decrypt and read any message sent to you via your public key.', + 'You can also encrypt and send messages to other people whose public keys you have.', ) + + # private keys self.status(None, - {ART_KEY_PAIR31A}, - {ART_KEY_PAIR3B+'\n',True}, - 3,'Allow me to explain.', - '(2A) is a separate encryption key generated by your password.', - '(2B) is a version of (2) which has been encrypted by (2A).', - "Because (2) will be destroyed, to rebuild it requires decrypting (2B) with (2A).", - ) - self.status( - None,{ART_KEY_PAIR5+'\n'}, - "However, in a final move, I will now destroy (2A), too.", - None,{ART_KEY_PAIR4Z1+'\n'}, - 'Why? Because now only you can regenerate it, by remembering the password which created it.', - # None,{ART_KEY_PAIR4Z1+'\n'}, - 'However, this also means that if you lose or forget your password, you\'re screwed.', - None,{ART_KEY_PAIR4Z2+'\n'}, - "Because without key (2A),you couldn never unlock (2B).", - None,{ART_KEY_PAIR4Z3+'\n'}, - "And without (2B) and (2A) together, you could never re-assemble the private key of (2).", - None,{ART_KEY_PAIR4Z42+'\n'}, - "And without (2), you couldn't read messages sent to your public key.", - - ) - self.status( - None,{ART_KEY_PAIR4ZZ}, - 'So choosing a password is an important thing!' + {CUBEKEY}, + 'So if someone were to steal your private key, they could read your mail and forge your signature.' + 'You you should never, ever give your private key to anyone.', + 'In fact, this key is so dangerous that we need to lock it away immediately.', + "We'll even throw away the key we use to lock this private key with!", + "How? By regenerating it each time from your password.", ) if not passphrase: @@ -293,37 +279,83 @@ class CLI(Logger): return passphrase def status_keymaker_part3(self,privkey,privkey_decr,privkey_encr,passphrase): - # self.status( - # None,{tw.indent(ART_KEY,' '*5)+'\n',True}, - # # None,{ART_+'\n',True}, - # 'Now that we have a hashed passphrase, we can generate the (2A) encryption key.', - # {ART_KEY_KEY2A,True,0.1}, - # '''This key (2A) is formed using Themis's high-level symmetric encryption key library, SecureCell using Seal mode.''', - # '''It uses the AES-256 encryption algorithm, which was developed by the U.S. National Institute of Standards and Technology (NIST) in 2001.''' - # ) - s1=self.print('Now that we have (2A), we can use it to encrypt the super-sensitive private key (2):',ret=True) - s2a = self.print(f"(2A) {make_key_discreet_str(passphrase)}",ret=True) - screen1 = f'''{s1}\n\n{ART_KEY_PAIR_SPLITTING1}\n{s2a}''' + self.status( + None,{tw.indent(ART_KEY,' '*5)+'\n',True}, + # None,{ART_+'\n',True}, + 'Now that we have a hashed passphrase, we can generate the (2A) encryption key.', + {ART_KEY_KEY2A,True,0.1}, + '''The key is formed using Themis's high-level symmetric encryption library: SecureCell, using Seal mode.''', + 'This key (2A) then uses the AES-256 encryption algorithm to encrypt the super-sensitive private key (2):' + ) + s0=str.center('[Encryption Process]',CLI_WIDTH) + s1=s0 + '\n\n' + self.print('Now that we have (2A), we can use it to encrypt the super-sensitive private key (2):',ret=True) + s2a = self.print(f"(2A) {make_key_discreet_str(passphrase)}",ret=True) s2 = self.print(f"(2) {make_key_discreet(privkey.data_b64)}",ret=True) - screen2 = f'''{s1}\n\n{ART_KEY_PAIR_SPLITTING2}\n{s2a}\n\n\n{s2}''' - - repr_privkey = repr(privkey).replace('] ',']\n') s2b = self.print(f"(2B) {make_key_discreet(b64encode(privkey_encr))}",ret=True) - screen3 = f'''{s1}\n\n{ART_KEY_PAIR_SPLITTING3}\n{s2a}\n\n\n{s2}\n\n\n{s2b}''' - + self.status( + # screen 1 + None,{f'{s1}'}, + False, + + # 2 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING1}'}, + {s2a,True}, + False, + + # 3 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING2}\n{s2a}'}, + {'\n'+s2,True}, + False, + + # 4 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING3}\n{s2a}\n\n{s2}'}, + {'\n'+s2b,True}, + False, + ) - self.status(None,s1) - self.status(None,{screen1}) - do_pause() - self.status(None,{screen2}) - do_pause() - self.status(None,{screen3}) - do_pause() + + shdr=str.center('[Decryption Process]',CLI_WIDTH) + '\n\n' + self.print('Once we have (2B), we don\'t need (2A) or (2) anymore. We can regenerate them!',ret=True) + from getpass import getpass + + passhash = None + while passhash!=passphrase: + res = self.status( + None,{shdr},False if passhash is None else True, + ("pass",self.print(f"Let's try. Re-type your password into @Hasher:",ret=True)+f" \n ",getpass) + ) + + passhash = self.persona.crypt_keys.hash(res.get('vals').get('pass').encode()) + if passhash!=passphrase: + self.status({' Looks like they don\'t match. Try again?'},False) + + self.status( + {' Excellent. We can now regenerate the decryption key:'},False, + {s2a,True},False, + ) + # self.status('great') + # shdr2= + self.status( + + # 2 + None,{f'{shdr}\n\n{ART_KEY_PAIR_SPLITTING1}'}, + {s2a,True}, + False, + + # 3 + # None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING2}\n{s2a}'}, + # {'\n'+s2,True}, + # False, + + # 4 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING4}\n{s2a}\n\n\n\n'}, + {'\n'+s2b,True}, + False, + ) @@ -338,4 +370,47 @@ def run_cli(): if __name__=='__main__': run_cli() - # asyncio.run(test_async()) \ No newline at end of file + # asyncio.run(test_async()) + + + + + + + + + + + + + + +""" +Outtakes + + self.status(None, + {ART_KEY_PAIR31A}, + {ART_KEY_PAIR3B+'\n',True}, + 3,'Allow me to explain.', + '(2A) is a separate encryption key generated by your password.', + '(2B) is a version of (2) which has been encrypted by (2A).', + "Because (2) will be destroyed, to rebuild it requires decrypting (2B) with (2A).", + ) + + + self.status( + None,{ART_KEY_PAIR5+'\n'}, + "However, in a final move, I will now destroy (2A), too.", + None,{ART_KEY_PAIR4Z1+'\n'}, + 'Why? Because now only you can regenerate it by remembering the password which created it.', + # None,{ART_KEY_PAIR4Z1+'\n'}, + 'However, this also means that if you lose or forget your password, you\'re screwed.', + None,{ART_KEY_PAIR4Z2+'\n'}, + "Because without key (2A),you couldn never unlock (2B).", + None,{ART_KEY_PAIR4Z3+'\n'}, + "And without (2B) and (2A) together, you could never re-assemble the private key of (2).", + None,{ART_KEY_PAIR4Z42+'\n'}, + "And without (2), you couldn't read messages sent to your public key.", + + ) +""" \ No newline at end of file diff --git a/komrade/cli/cli0.py b/komrade/cli/cli0.py new file mode 100644 index 0000000..17a424f --- /dev/null +++ b/komrade/cli/cli0.py @@ -0,0 +1,415 @@ +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 import * +import art +import textwrap as tw + + + +class CLI(Logger): + ROUTES = { + 'help':'see help messages', + 'register':'register new user', + 'login':'log back in' + } + + def __init__(self,name='',cmd='',persona=None): + self.name=name + self.cmd=cmd + self.persona=persona + + def run(self,inp='',name=''): + self.name=name + clear_screen() + # self.boot() + # self.help() + + if inp: self.route(inp) + + while True: + try: + inp=input(f'@{self.name if self.name else "?"}: ') + except KeyboardInterrupt: + exit() + self.route(inp) + #await asyncio.sleep(0.5) + + def route(self,inp): + inp=inp.strip() + if not inp.startswith('/'): return + cmd=inp.split()[0] + dat=inp[len(cmd):].strip() + cmd=cmd[1:] + if cmd in self.ROUTES and hasattr(self,cmd): + f=getattr(self,cmd) + return f(dat) + + def boot(self,indent=5): + logo=art.text2art(CLI_TITLE,font=CLI_FONT) + # logo=make_key_discreet_str(logo,chance_redacted=0.1) #.decode() + logo=tw.indent(logo, ' '*indent) + scan_print(logo,max_pause=0.005) + + def help(self): + print() + for cmd,info in self.ROUTES.items(): + print(f' /{cmd}: {info}') + # print('\n') + print('\n') + + def intro(self): + self.status(None,) + + def register(self,dat): + self.persona = Persona(self.name) + self.persona.register() + + + + + ### DIALOGUES + + # hello, op? + def status_keymaker_part1(self,name): + self.status(None,{ART_OLDPHONE4+'\n',True},3) #,scan=False,width=None,pause=None,clear=None) + + nm=name if name else '?' + self.status( + f'\n\n\n@{nm}: Uh yes hello, Operator? I would like to join Komrade, the socialist network. Could you patch me through?',clear=False) + + while not name: + name=self.status(('name','@TheTelephone: Of course, Komrade...?\n@')).get('vals').get('name').strip() + print() + + self.status( + f'@TheTelephone: Of course, Komrade @{name}. A fine name.', + + '''@TheTelephone: However, I'm just the local operator who lives on your device; my only job is to communicate with the remote operator securely.''', + + '''Komrade @TheOperator lives on the deep web. She's the one you want to speak with.''', + + None,{ART_OLDPHONE4},f'''@{name}: Hm, ok. Well, could you patch me through to the remote operator then?''', + + f'''@{TELEPHONE_NAME}: I could, but it's not safe yet. Your information could be exposed. You need to cut your encryption keys first.''', + + f'@{name}: Fine, but how do I do that?', + + f'@{TELEPHONE_NAME}: Visit the Keymaker.', + + clear=False,pause=True) + + ### KEYMAKER + self.status(None,{tw.indent(ART_KEY,' '*5)+'\n',True},3) #,clear=False,indent=10,pause=False) + # convo + self.status( + f'\n@{name}: Hello, Komrade @Keymaker? I would like help forging a new set of keys.', + + f'@Keymaker: Of course, Komrade @{name}.', + ) + + self.status( + 'I will cut for you two matching keys, part of an "asymmetric" pair.', + 'Please, watch me work.', + + None,{tw.indent(ART_KEY,' '*5)+'\n'}, + + 'I use a high-level cryptographic function from Themis, a well-respected open-source cryptography library.', + 'I use the iron-clad Elliptic Curve algorthm to generate the asymmetric keypair.', + '> GenerateKeyPair(KEY_PAIR_TYPE.EC)', + 3 + ) + self.status( + None, + {ART_KEY_PAIR,True} + ) #,clear=False,indent=10,pause=False) + + return name + + + + def status_keymaker_part2(self,name,passphrase,pubkey,privkey,hasher,persona): + from getpass import getpass + # gen what we need + uri_id = pubkey.data_b64 + qr_str = get_qr_str(uri_id) + qr_path = os.path.join(PATH_QRCODES,name+'.png') + + # what are pub/priv? + + # self.status( + # None,{ART_KEY_PAIR}, + # 'A matching set of keys have been generated.', + # None,{ART_KEY_PAIR2A+'\nA matching set of keys have been generated.'+'\n'}, + # 'First, I have made a "public key" which you can share with anyone:', + # f'(1) {pubkey.data_b64.decode()}', + # 'This key is a randomly-generated binary string, which acts as your "address" on Komrade.', + # 'With it, someone can write you an encrypted message which only you can read.' + # ) + # self.status( + # None,{ART_KEY_PAIR2A}, + # f'You can share your public key by copy/pasting it to them over a secure channel (e.g. Signal).', + # 'Or, you can share it as a QR code, especially IRL:', + # {qr_str+'\n\n',True,5}, + # f'\n\n(If registration is successful, this QR code be saved as an image to your device at: {qr_path}.)' + # ) + + # private keys + self.status(None, + {ART_KEY_PAIR2B}, + 'Second, I have cut a matching "private key".', + "It's too dangerous to show in full, so I've redacted most of it:", + f'(2) {make_key_discreet(privkey.data_b64,0.3)}', + 'With it, you can decrypt any message sent to you via your public key.', + 'You can also encrypt messages ', + ) + + # private keys + self.status(None, + {CUBEKEY}, + 'You you should never, ever give your private key to anyone.', + 'In fact, this key is so dangerous that we need to lock it away immediately.', + "We'll even throw away the key we use to lock this private key with!", + "How? By regenerating it each time from your password.", + ) + + if not passphrase: + from getpass import getpass + self.status( + 'And it looks like you haven\'t yet chosen a password.', + 3,"Don't tell it to me! Never tell it to anyone.", + "Ideally, don't even save it on your computer; just remember it, or write it down on paper.", + "Instead, whisper it to Komrade @Hasher, who scrambles information '1-way', like a blender.", + ) + + res = self.status(None, + {indent_str(ART_FROG_BLENDER,10),True}, + "@Keymaker: Go ahead, try it. Type anything to @Hasher.", + ('str_to_hash',f'@{name}: ',input) + ) + str_to_hash = res.get('vals').get('str_to_hash') + hashed_str1 = hasher(str_to_hash.encode()) + res = self.status( + '@Hasher: '+hashed_str1 + ) + res = self.status( + '@Keymaker: Whatever you typed, there\'s no way to reconstruct it from that garbled mess.', + 'But whatever you typed will always produce the *same* garbled mess.', + ('str_to_hash',f'Try typing the exact same thing over again:\n@{name}: ',input) + ) + str_to_hash = res.get('vals').get('str_to_hash') + hashed_str2 = hasher(str_to_hash.encode()) + res = self.status( + '@Hasher: '+hashed_str2 + ) + if hashed_str1==hashed_str2: + self.status('See how the hashed values are also exactly the same?') + else: + self.status('See how the hashed values have also changed?') + + res = self.status( + ('str_to_hash',f'Now try typing something just a little bit different:\n@{name}: ',input) + ) + str_to_hash = res.get('vals').get('str_to_hash') + hashed_str3 = hasher(str_to_hash.encode()) + res = self.status( + '@Hasher: '+hashed_str3 + ) + if hashed_str2==hashed_str3: + self.status('See how the hashed values are also the same?') + else: + self.status('See how the hashed values have also changed?') + + + self.status( + None,{indent_str(ART_FROG_BLENDER,10)}, + '@Keymaker: Behind the scenes, @Hasher is using the SHA-256 hashing function, which was designed by the NSA.', + 'But @Hasher also adds a secret "salt" to the recipe, as it\'s called.', + 'To whatever you type in, @Hasher adds a secret phrase: another random string of characters which never changes.', + "By doing so, the hash output is \"salted\": made even more idiosyncratic to outside observers.", + ) + + self.status( + None,{indent_str(ART_FROG_BLENDER,10)}, + f"I've taken the liberty of generating a random secret for your device, which I show here mostly redacted:", + make_key_discreet_str(persona.crypt_keys.secret.decode(),0.25), + 'The full version of this secret is silently added to every input you type into @Hasher.', + "I've saved this secret phrase to a hidden location on your device hardware.", + ) + + self.status( + None,{indent_str(ART_FROG_BLENDER,10)}, + 'However, this means that you will be unable to log in to your account from any other device.', + 'This limitation provides yet another level of hardware protection against network attacks.', + 'However, you can always choose (not recommended) to the secret file with another device by a secure channel.', + 3,f'But, please excuse me Komrade @{name} -- I digress.' + ) + + while not passphrase: + res = self.status(None, + {indent_str(ART_FROG_BLENDER,10)}, + "@Keymaker: Please type your chosen password into @Hasher.", + ('str_to_hash',f'\n@{name}: ',getpass), + pause=False + ) + str_to_hash = res.get('vals').get('str_to_hash') + hashed_pass1 = hasher(str_to_hash.encode()) + res = self.status( + '\n@Hasher: '+hashed_pass1, + pause=False + ) + res = self.status( + '\nNow type in the same password one more time to verify it:', + ('str_to_hash',f'\n@{name}: ',getpass), + pause=False + ) + str_to_hash = res.get('vals').get('str_to_hash') + hashed_pass2 = hasher(str_to_hash.encode()) + res = self.status( + '\n@Hasher: '+hashed_pass2, + pause=False + ) + + if hashed_pass1==hashed_pass2: + self.status('','@Keymaker: Excellent. The passwords clearly matched, because the hashed values matched.',pause=False) + passphrase = hashed_pass1 + else: + self.status('@Keymaker: A pity. It looks like the passwords didn\'t match, since the hashed values didn\'t match either. Try again?') + + return passphrase + + def status_keymaker_part3(self,privkey,privkey_decr,privkey_encr,passphrase): + self.status( + None,{tw.indent(ART_KEY,' '*5)+'\n',True}, + # None,{ART_+'\n',True}, + 'Now that we have a hashed passphrase, we can generate the (2A) encryption key.', + {ART_KEY_KEY2A,True,0.1}, + '''The key is formed using Themis's high-level symmetric encryption library: SecureCell, using Seal mode.''', + 'This key (2A) then uses the AES-256 encryption algorithm to encrypt the super-sensitive private key (2):' + ) + + s0=str.center('[Encryption Process]',CLI_WIDTH) + s1=s0 + '\n\n' + self.print('Now that we have (2A), we can use it to encrypt the super-sensitive private key (2):',ret=True) + s2a = self.print(f"(2A) {make_key_discreet_str(passphrase)}",ret=True) + s2 = self.print(f"(2) {make_key_discreet(privkey.data_b64)}",ret=True) + s2b = self.print(f"(2B) {make_key_discreet(b64encode(privkey_encr))}",ret=True) + self.status( + # screen 1 + None,{f'{s1}'}, + False, + + # 2 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING1}'}, + {s2a,True}, + False, + + # 3 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING2}\n{s2a}'}, + {'\n'+s2,True}, + False, + + # 4 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING3}\n{s2a}\n\n{s2}'}, + {'\n'+s2b,True}, + False, + ) + + + shdr=str.center('[Decryption Process]',CLI_WIDTH) + '\n\n' + self.print('Once we have (2B), we don\'t need (2A) or (2) anymore. We can regenerate them!',ret=True) + from getpass import getpass + + passhash = None + + while passhash!=passphrase: + res = self.status( + None,{shdr},False if passhash is None else True, + + ("pass",self.print(f"Let's try. Re-type your password into @Hasher:",ret=True)+f" \n ",getpass) + ) + + passhash = self.persona.crypt_keys.hash(res.get('vals').get('pass').encode()) + if passhash!=passphrase: + self.status({' Looks like they don\'t match. Try again?'},False) + + + self.status( + {' Excellent. We can now regenerate the decryption key:'},False, + {s2a,True},False, + ) + + # self.status('great') + # shdr2= + self.status( + + # 2 + None,{f'{shdr}\n\n{ART_KEY_PAIR_SPLITTING1}'}, + {s2a,True}, + False, + + # 3 + # None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING2}\n{s2a}'}, + # {'\n'+s2,True}, + # False, + + # 4 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING4}\n{s2a}\n\n\n\n'}, + {'\n'+s2b,True}, + False, + ) + + + + + + + + +def run_cli(): + cli = CLI() + cli.run('/register','elon') #'/register',name='elon') + +if __name__=='__main__': + run_cli() + # asyncio.run(test_async()) + + + + + + + + + + + + + + +""" +Outtakes + + self.status(None, + {ART_KEY_PAIR31A}, + {ART_KEY_PAIR3B+'\n',True}, + 3,'Allow me to explain.', + '(2A) is a separate encryption key generated by your password.', + '(2B) is a version of (2) which has been encrypted by (2A).', + "Because (2) will be destroyed, to rebuild it requires decrypting (2B) with (2A).", + ) + + + self.status( + None,{ART_KEY_PAIR5+'\n'}, + "However, in a final move, I will now destroy (2A), too.", + None,{ART_KEY_PAIR4Z1+'\n'}, + 'Why? Because now only you can regenerate it by remembering the password which created it.', + # None,{ART_KEY_PAIR4Z1+'\n'}, + 'However, this also means that if you lose or forget your password, you\'re screwed.', + None,{ART_KEY_PAIR4Z2+'\n'}, + "Because without key (2A),you couldn never unlock (2B).", + None,{ART_KEY_PAIR4Z3+'\n'}, + "And without (2B) and (2A) together, you could never re-assemble the private key of (2).", + None,{ART_KEY_PAIR4Z42+'\n'}, + "And without (2), you couldn't read messages sent to your public key.", + + ) +""" \ No newline at end of file diff --git a/komrade/cli/cli_curses.py b/komrade/cli/cli_curses.py new file mode 100644 index 0000000..fbe30da --- /dev/null +++ b/komrade/cli/cli_curses.py @@ -0,0 +1,483 @@ +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 import * +import art +import textwrap as tw +import curses,time,random + +CLI_NCOLS = 50 +CLI_NROWS = 100 + + +class CLI(Logger): + ROUTES = { + 'help':'see help messages', + 'register':'register new user', + 'login':'log back in' + } + + def __init__(self,name='',cmd='',persona=None,stdscr=None): + self.name=name + self.cmd=cmd + self.persona=persona + + self.boot(stdscr=stdscr) + + + + def boot(self,indent=5,stdscr=None): + + if not stdscr: + self.stdscr = stdscr = curses.initscr() + else: + self.stdscr = stdscr + + # curses.noecho() + # curses.cbreak() + # stdscr.keypad(True) + + stdscr.clear() + stdscr.refresh() + curses.start_color() + curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE) + + + self.win = curses.newwin( + CLI_NROWS, + CLI_NCOLS, + ) + + def shutdown(self): + # curses.nocbreak() + # stdscr.keypad(False) + # curses.echo() + exit('Goodbye') + + def scan_print(self,string,start_x=0,start_y=0): + for li,ln in enumerate(string.split('\n')): + for xi,x in enumerate(ln): + pos_x = start_x + xi + pos_y = start_y + li + self.stdscr.addstr(pos_y,pos_x,x) + time.sleep(random.uniform(0,0.01)) + self.stdscr.refresh() + + + def run(self,inp='',name=''): + self.name=name + clear_screen() + # self.boot() + self.logo() + + # self.help() + inp = chr(self.stdscr.getch()) + + if inp: self.route(inp) + + while True: + i = random.randint(0,CLI_NCOLS) + j = random.randint(0,CLI_NCOLS) + self.stdscr.addstr(i,j,'!') + + try: + # inp=input(f'@{self.name if self.name else "?"}: ') + inp = k = chr(self.stdscr.getch()) + except KeyboardInterrupt: + self.shutdown() + self.route(inp) + self.stdscr.refresh() + #await asyncio.sleep(0.5) + + + + + # @property + def logo(self): + import textwrap as tw + logo=art.text2art(CLI_TITLE,font=CLI_FONT) + self.scan_print(logo) + return logo + + + + def route(self,inp): + inp=inp.strip() + if not inp.startswith('/'): return + cmd=inp.split()[0] + dat=inp[len(cmd):].strip() + cmd=cmd[1:] + if cmd in self.ROUTES and hasattr(self,cmd): + f=getattr(self,cmd) + return f(dat) + + + def help(self): + print() + for cmd,info in self.ROUTES.items(): + print(f' /{cmd}: {info}') + # print('\n') + print('\n') + + def intro(self): + self.status(None,) + + def register(self,dat): + self.persona = Persona(self.name) + self.persona.register() + + + + + ### DIALOGUES + + # hello, op? + def status_keymaker_part1(self,name): + self.status(None,{ART_OLDPHONE4+'\n',True},3) #,scan=False,width=None,pause=None,clear=None) + + nm=name if name else '?' + self.status( + f'\n\n\n@{nm}: Uh yes hello, Operator? I would like to join Komrade, the socialist network. Could you patch me through?',clear=False) + + while not name: + name=self.status(('name','@TheTelephone: Of course, Komrade...?\n@')).get('vals').get('name').strip() + print() + + self.status( + f'@TheTelephone: Of course, Komrade @{name}. A fine name.', + + '''@TheTelephone: However, I'm just the local operator who lives on your device; my only job is to communicate with the remote operator securely.''', + + '''Komrade @TheOperator lives on the deep web. She's the one you want to speak with.''', + + None,{ART_OLDPHONE4},f'''@{name}: Hm, ok. Well, could you patch me through to the remote operator then?''', + + f'''@{TELEPHONE_NAME}: I could, but it's not safe yet. Your information could be exposed. You need to cut your encryption keys first.''', + + f'@{name}: Fine, but how do I do that?', + + f'@{TELEPHONE_NAME}: Visit the Keymaker.', + + clear=False,pause=True) + + ### KEYMAKER + self.status(None,{tw.indent(ART_KEY,' '*5)+'\n',True},3) #,clear=False,indent=10,pause=False) + # convo + self.status( + f'\n@{name}: Hello, Komrade @Keymaker? I would like help forging a new set of keys.', + + f'@Keymaker: Of course, Komrade @{name}.', + ) + + self.status( + 'I will cut for you two matching keys, part of an "asymmetric" pair.', + 'Please, watch me work.', + + None,{tw.indent(ART_KEY,' '*5)+'\n'}, + + 'I use a high-level cryptographic function from Themis, a well-respected open-source cryptography library.', + 'I use the iron-clad Elliptic Curve algorthm to generate the asymmetric keypair.', + '> GenerateKeyPair(KEY_PAIR_TYPE.EC)', + 3 + ) + self.status( + None, + {ART_KEY_PAIR,True} + ) #,clear=False,indent=10,pause=False) + + return name + + + + def status_keymaker_part2(self,name,passphrase,pubkey,privkey,hasher,persona): + from getpass import getpass + # gen what we need + uri_id = pubkey.data_b64 + qr_str = get_qr_str(uri_id) + qr_path = os.path.join(PATH_QRCODES,name+'.png') + + # what are pub/priv? + + # self.status( + # None,{ART_KEY_PAIR}, + # 'A matching set of keys have been generated.', + # None,{ART_KEY_PAIR2A+'\nA matching set of keys have been generated.'+'\n'}, + # 'First, I have made a "public key" which you can share with anyone:', + # f'(1) {pubkey.data_b64.decode()}', + # 'This key is a randomly-generated binary string, which acts as your "address" on Komrade.', + # 'With it, someone can write you an encrypted message which only you can read.' + # ) + # self.status( + # None,{ART_KEY_PAIR2A}, + # f'You can share your public key by copy/pasting it to them over a secure channel (e.g. Signal).', + # 'Or, you can share it as a QR code, especially IRL:', + # {qr_str+'\n\n',True,5}, + # f'\n\n(If registration is successful, this QR code be saved as an image to your device at: {qr_path}.)' + # ) + + # private keys + self.status(None, + {ART_KEY_PAIR2B}, + 'Second, I have cut a matching "private key".', + "It's too dangerous to show in full, so I've redacted most of it:", + f'(2) {make_key_discreet(privkey.data_b64,0.3)}', + 'With it, you can decrypt any message sent to you via your public key.', + 'You can also encrypt messages ', + ) + + # private keys + self.status(None, + {CUBEKEY}, + 'You you should never, ever give your private key to anyone.', + 'In fact, this key is so dangerous that we need to lock it away immediately.', + "We'll even throw away the key we use to lock this private key with!", + "How? By regenerating it each time from your password.", + ) + + if not passphrase: + from getpass import getpass + self.status( + 'And it looks like you haven\'t yet chosen a password.', + 3,"Don't tell it to me! Never tell it to anyone.", + "Ideally, don't even save it on your computer; just remember it, or write it down on paper.", + "Instead, whisper it to Komrade @Hasher, who scrambles information '1-way', like a blender.", + ) + + res = self.status(None, + {indent_str(ART_FROG_BLENDER,10),True}, + "@Keymaker: Go ahead, try it. Type anything to @Hasher.", + ('str_to_hash',f'@{name}: ',input) + ) + str_to_hash = res.get('vals').get('str_to_hash') + hashed_str1 = hasher(str_to_hash.encode()) + res = self.status( + '@Hasher: '+hashed_str1 + ) + res = self.status( + '@Keymaker: Whatever you typed, there\'s no way to reconstruct it from that garbled mess.', + 'But whatever you typed will always produce the *same* garbled mess.', + ('str_to_hash',f'Try typing the exact same thing over again:\n@{name}: ',input) + ) + str_to_hash = res.get('vals').get('str_to_hash') + hashed_str2 = hasher(str_to_hash.encode()) + res = self.status( + '@Hasher: '+hashed_str2 + ) + if hashed_str1==hashed_str2: + self.status('See how the hashed values are also exactly the same?') + else: + self.status('See how the hashed values have also changed?') + + res = self.status( + ('str_to_hash',f'Now try typing something just a little bit different:\n@{name}: ',input) + ) + str_to_hash = res.get('vals').get('str_to_hash') + hashed_str3 = hasher(str_to_hash.encode()) + res = self.status( + '@Hasher: '+hashed_str3 + ) + if hashed_str2==hashed_str3: + self.status('See how the hashed values are also the same?') + else: + self.status('See how the hashed values have also changed?') + + + self.status( + None,{indent_str(ART_FROG_BLENDER,10)}, + '@Keymaker: Behind the scenes, @Hasher is using the SHA-256 hashing function, which was designed by the NSA.', + 'But @Hasher also adds a secret "salt" to the recipe, as it\'s called.', + 'To whatever you type in, @Hasher adds a secret phrase: another random string of characters which never changes.', + "By doing so, the hash output is \"salted\": made even more idiosyncratic to outside observers.", + ) + + self.status( + None,{indent_str(ART_FROG_BLENDER,10)}, + f"I've taken the liberty of generating a random secret for your device, which I show here mostly redacted:", + make_key_discreet_str(persona.crypt_keys.secret.decode(),0.25), + 'The full version of this secret is silently added to every input you type into @Hasher.', + "I've saved this secret phrase to a hidden location on your device hardware.", + ) + + self.status( + None,{indent_str(ART_FROG_BLENDER,10)}, + 'However, this means that you will be unable to log in to your account from any other device.', + 'This limitation provides yet another level of hardware protection against network attacks.', + 'However, you can always choose (not recommended) to the secret file with another device by a secure channel.', + 3,f'But, please excuse me Komrade @{name} -- I digress.' + ) + + while not passphrase: + res = self.status(None, + {indent_str(ART_FROG_BLENDER,10)}, + "@Keymaker: Please type your chosen password into @Hasher.", + ('str_to_hash',f'\n@{name}: ',getpass), + pause=False + ) + str_to_hash = res.get('vals').get('str_to_hash') + hashed_pass1 = hasher(str_to_hash.encode()) + res = self.status( + '\n@Hasher: '+hashed_pass1, + pause=False + ) + res = self.status( + '\nNow type in the same password one more time to verify it:', + ('str_to_hash',f'\n@{name}: ',getpass), + pause=False + ) + str_to_hash = res.get('vals').get('str_to_hash') + hashed_pass2 = hasher(str_to_hash.encode()) + res = self.status( + '\n@Hasher: '+hashed_pass2, + pause=False + ) + + if hashed_pass1==hashed_pass2: + self.status('','@Keymaker: Excellent. The passwords clearly matched, because the hashed values matched.',pause=False) + passphrase = hashed_pass1 + else: + self.status('@Keymaker: A pity. It looks like the passwords didn\'t match, since the hashed values didn\'t match either. Try again?') + + return passphrase + + + + + def status_keymaker_part3(self,privkey,privkey_decr,privkey_encr,passphrase): + self.status( + None,{tw.indent(ART_KEY,' '*5)+'\n',True}, + # None,{ART_+'\n',True}, + 'Now that we have a hashed passphrase, we can generate the (2A) encryption key.', + {ART_KEY_KEY2A,True,0.1}, + '''The key is formed using Themis's high-level symmetric encryption library: SecureCell, using Seal mode.''', + 'This key (2A) then uses the AES-256 encryption algorithm to encrypt the super-sensitive private key (2):' + ) + + s0=str.center('[Encryption Process]',CLI_WIDTH) + s1=s0 + '\n\n' + self.print('Now that we have (2A), we can use it to encrypt the super-sensitive private key (2):',ret=True) + s2a = self.print(f"(2A) {make_key_discreet_str(passphrase)}",ret=True) + s2 = self.print(f"(2) {make_key_discreet(privkey.data_b64)}",ret=True) + s2b = self.print(f"(2B) {make_key_discreet(b64encode(privkey_encr))}",ret=True) + self.status( + # screen 1 + None,{f'{s1}'}, + False, + + # 2 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING1}'}, + {s2a,True}, + False, + + # 3 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING2}\n{s2a}'}, + {'\n'+s2,True}, + False, + + # 4 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING3}\n{s2a}\n\n{s2}'}, + {'\n'+s2b,True}, + False, + ) + + + shdr=str.center('[Decryption Process]',CLI_WIDTH) + '\n\n' + self.print('Once we have (2B), we don\'t need (2A) or (2) anymore. We can regenerate them!',ret=True) + from getpass import getpass + + passhash = None + + while passhash!=passphrase: + res = self.status( + None,{shdr},False if passhash is None else True, + + ("pass",self.print(f"Let's try. Re-type your password into @Hasher:",ret=True)+f" \n ",getpass) + ) + + passhash = self.persona.crypt_keys.hash(res.get('vals').get('pass').encode()) + if passhash!=passphrase: + self.status({' Looks like they don\'t match. Try again?'},False) + + + self.status( + {' Excellent. We can now regenerate the decryption key:'},False, + {s2a,True},False, + ) + + # self.status('great') + # shdr2= + self.status( + + # 2 + None,{f'{shdr}\n\n{ART_KEY_PAIR_SPLITTING1}'}, + {s2a,True}, + False, + + # 3 + # None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING2}\n{s2a}'}, + # {'\n'+s2,True}, + # False, + + # 4 + None,{f'{s1}\n\n{ART_KEY_PAIR_SPLITTING4}\n{s2a}\n\n\n\n'}, + {'\n'+s2b,True}, + False, + ) + + + + + +def main(): + import curses + curses.wrapper(run_cli) + + +def run_cli_curses(stdscr): + cli = CLI(stdscr=stdscr) + cli.run('/register','elon') #'/register',name='elon') + +if __name__=='__main__': + # run_cli() + # asyncio.run(test_async()) + main() + + + + + + + + + + + + + +""" +Outtakes + + self.status(None, + {ART_KEY_PAIR31A}, + {ART_KEY_PAIR3B+'\n',True}, + 3,'Allow me to explain.', + '(2A) is a separate encryption key generated by your password.', + '(2B) is a version of (2) which has been encrypted by (2A).', + "Because (2) will be destroyed, to rebuild it requires decrypting (2B) with (2A).", + ) + + + self.status( + None,{ART_KEY_PAIR5+'\n'}, + "However, in a final move, I will now destroy (2A), too.", + None,{ART_KEY_PAIR4Z1+'\n'}, + 'Why? Because now only you can regenerate it by remembering the password which created it.', + # None,{ART_KEY_PAIR4Z1+'\n'}, + 'However, this also means that if you lose or forget your password, you\'re screwed.', + None,{ART_KEY_PAIR4Z2+'\n'}, + "Because without key (2A),you couldn never unlock (2B).", + None,{ART_KEY_PAIR4Z3+'\n'}, + "And without (2B) and (2A) together, you could never re-assemble the private key of (2).", + None,{ART_KEY_PAIR4Z42+'\n'}, + "And without (2), you couldn't read messages sent to your public key.", + + ) +""" \ No newline at end of file diff --git a/komrade/constants.py b/komrade/constants.py index 87e06ec..97901f9 100644 --- a/komrade/constants.py +++ b/komrade/constants.py @@ -182,10 +182,11 @@ DEFAULT_USER_SETTINGS = { } SHOW_LOG = 1 -SHOW_STATUS = 1 +SHOW_STATUS = 0 PAUSE_LOGGER = 1 CLI_TITLE = 'KOMRADE' CLI_FONT = 'clr5x6'#'colossal' -CLI_WIDTH = STATUS_LINE_WIDTH = 50 \ No newline at end of file +CLI_WIDTH = STATUS_LINE_WIDTH = 50 + diff --git a/komrade/utils.py b/komrade/utils.py index d53204d..407f273 100644 --- a/komrade/utils.py +++ b/komrade/utils.py @@ -44,11 +44,15 @@ def dict_format(d, tab=0): s = ['{\n\n'] for k,v in sorted(d.items()): + v=reppr(v) + #print(k,v,type(v)) + if isinstance(v, dict): v = dict_format(v, tab+1) else: v = repr(v) + # s.append('%s%r: %s (%s),\n' % (' '*tab, k, v, type(v).__name__)) s.append('%s%r: %s,\n\n' % (' '*tab, k, reppr(v))) s.append('%s}' % (' '*(tab-2))) @@ -63,7 +67,7 @@ class Logger(object): calframe = inspect.getouterframes(curframe, 2) mytype = type(self).__name__ caller = calframe[1][3] - log(f'\n[{mytype}.{caller}()]',*x) + log(f'[{mytype}.{caller}()]\n\n',*x) # try: if pause: do_pause() @@ -94,7 +98,6 @@ class Logger(object): paras=[] res={} for para in msg: - indentstr=' '*indent plen = para if type(para)==int or type(para)==float else None if type(para) in {int,float}: plen=int(para) @@ -109,9 +112,9 @@ class Logger(object): elif para is None: clear_screen() elif para is False: - pass + do_pause() elif para is True: - pass + print() elif type(para) is set: # logo/image pl = [x for x in para if type(x)==str] txt=pl[0] @@ -131,8 +134,6 @@ class Logger(object): res[k]=ans elif type(para) is dict: print(dict_format(para,tab=tab)) - elif para is 0: - do_pause() elif pause: self.print(para,flush=True,end=end if end else '\n',scan=scan,indent=indent) paras+=[para] @@ -261,7 +262,7 @@ def capture_stdout(func): -def scan_print(xstr,min_pause=0,max_pause=.001,speed=1): +def scan_print(xstr,min_pause=0,max_pause=.01,speed=1): import random,time for c in xstr: print(c,end='',flush=True)