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/komrade/cli/cli.py

256 lines
9.0 KiB
Python

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):
self.name=''
self.cmd=''
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_bowdlerize=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_intro(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 forge 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}.',
)
return name
def status_keymaker_body(self,name,passphrase,pubkey,privkey,hasher):
# 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.',
# '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)
# 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}.)'
# )
# 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:'
# )
# 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_PAIR4Z1},
'So choosing a password is an important thing!'
)
if not passphrase:
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,
{ART_FROG_BLENDER,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_str = hasher(str_to_hash.encode())
res = self.status(
'@Hasher: '+hashed_str
)
res = self.status(
'@Keymaker: See? Ok, now type in a password.'
('str_to_hash',f'@{name}: ',getpass)
)
str_to_hash = res.get('vals').get('str_to_hash')
hashed_pass1 = hasher(str_to_hash.encode())
res = self.status(
'@Hasher: '+hashed_pass1
)
res = self.status(
'@Keymaker: Whatever you entered, it\'s already forgotten. That hashed mess is all that remains.',
'Now type in the same password one more time to verify it:',
('str_to_hash',f'@{name}: ',getpass)
)
str_to_hash = res.get('vals').get('str_to_hash')
hashed_pass2 = hasher(str_to_hash.encode())
res = self.status(
'@Hasher: '+hashed_pass2
)
if hashed_pass1==hashed_pass2:
self.status('The passwords matched.')
else:
self.status('The passwords did not match.')
def run_cli():
cli = CLI()
cli.run('/register','elon') #'/register',name='elon')
if __name__=='__main__':
run_cli()
# asyncio.run(test_async())