def new_password(cls, purpose: str = "master password") -> str: """Prompt the user to enter and confirm a new password.""" password_1 = pwd_prompt(f"Enter a new {purpose}: ") if password_1 == GENERATE: pwd_bit_strength, password_1 = MasterKey.generate_master_password() m_print([ f"Generated a {pwd_bit_strength}-bit password:"******"Write down this password and dispose of the copy once you remember it.", "Press <Enter> to continue." ], manual_proceed=True, box=True, head=1, tail=1) reset_terminal() password_2 = password_1 else: password_2 = pwd_prompt(f"Confirm the {purpose}: ", repeat=True) if password_1 == password_2: return password_1 m_print("Error: Passwords did not match. Try again.", head=1, tail=1) print_on_previous_line(delay=1, reps=7) return cls.new_password(purpose)
def redraw_file_win(self) -> None: """Draw file transmission window progress bars.""" # Columns c1 = ['File name'] c2 = ['Size'] c3 = ['Sender'] c4 = ['Complete'] for i, p in enumerate(self.packet_list): if p.type == FILE and len(p.assembly_pt_list) > 0: c1.append(p.name) c2.append(p.size) c3.append(p.contact.nick) c4.append(f"{len(p.assembly_pt_list) / p.packets * 100:.2f}%") if not len(c1) > 1: c_print("No file transmissions currently in progress.", head=1, tail=1) print_on_previous_line(reps=3, delay=0.1) return None lst = [] for name, size, sender, percent, in zip(c1, c2, c3, c4): lst.append('{0:{1}} {2:{3}} {4:{5}} {6:{7}}'.format( name, max(len(v) for v in c1) + CONTACT_LIST_INDENT, size, max(len(v) for v in c2) + CONTACT_LIST_INDENT, sender, max(len(v) for v in c3) + CONTACT_LIST_INDENT, percent, max(len(v) for v in c4) + CONTACT_LIST_INDENT)) lst.insert(1, get_terminal_width() * '─') print('\n' + '\n'.join(lst) + '\n') print_on_previous_line(reps=len(lst) + 2, delay=0.1)
def redraw(self): """Draw file window frame.""" ft_found = False line_ctr = 0 longest_title = 0 tty_w = get_tty_w() for p in self.packet_list: if p.type == 'file' and len(p.assembly_pt_list) > 0: title = "{} ({}) from {} ".format(p.f_name, p.f_size, p.contact.nick) longest_title = max(longest_title, len(title)) for p in self.packet_list: if p.type == 'file' and len(p.assembly_pt_list) > 0: line_ctr += 1 ft_found = True title = "{} ({}) from {} ".format(p.f_name, p.f_size, p.contact.nick) title += (longest_title - len(title)) * ' ' bar_len = max(tty_w - (4 + len(title)), 1) ready = int((len(p.assembly_pt_list) / p.f_packets) * bar_len) missing = bar_len - ready bar = title + '[' + (ready - 1) * '=' + '>' + missing * ' ' + ']' print(bar) print_on_previous_line(reps=line_ctr) if not ft_found: c_print("No file transmissions currently in progress.", head=1, tail=1) print_on_previous_line(reps=3)
def rxp_show_sys_win( user_input: 'UserInput', window: 'TxWindow', settings: 'Settings', queues: 'QueueDict', ) -> None: """\ Display a system window on Receiver Program until the user presses Enter. Receiver Program has a dedicated window, WIN_UID_LOCAL, for system messages that shows information about received commands, status messages etc. Receiver Program also has another window, WIN_UID_FILE, that shows progress of file transmission from contacts that have traffic masking enabled. """ cmd = user_input.plaintext.split()[0] win_uid = dict(cmd=WIN_UID_COMMAND, fw=WIN_UID_FILE)[cmd] command = WIN_SELECT + win_uid queue_command(command, settings, queues) try: m_print(f"<Enter> returns Receiver to {window.name}'s window", manual_proceed=True, box=True) except (EOFError, KeyboardInterrupt): pass print_on_previous_line(reps=4, flush=True) command = WIN_SELECT + window.uid queue_command(command, settings, queues)
def init_entropy() -> None: """Wait until Kernel CSPRNG is sufficiently seeded. Wait until entropy_avail file states that system has at least 512 bits of entropy. The headroom allows room for error in accuracy of entropy collector's entropy estimator; As long as input has at least 4 bits per byte of actual entropy, /dev/urandom will be sufficiently seeded when it is allowed to generate keys. """ clear_screen() phase("Waiting for Kernel CSPRNG random pool to fill up", head=1) ent_avail = 0 threshold = 512 while ent_avail < threshold: try: with open('/proc/sys/kernel/random/entropy_avail') as f: value = f.read() ent_avail = int(value.strip()) c_print("{}/{}".format(ent_avail, threshold)) print_on_previous_line(delay=0.01) except (KeyboardInterrupt, EOFError): pass print_on_previous_line() phase("Waiting for Kernel CSPRNG random pool to fill up") phase("Done")
def search_serial_interface(self) -> str: """Search for a serial interface.""" if self.settings.session_usb_serial_adapter: search_announced = False if not self.init_found: phase("Searching for USB-to-serial interface", offset=len('Found')) while True: for f in sorted(os.listdir('/dev/')): if f.startswith('ttyUSB'): if self.init_found: time.sleep(1) phase('Found', done=True) if self.init_found: print_on_previous_line(reps=2) self.init_found = True return f'/dev/{f}' time.sleep(0.1) if self.init_found and not search_announced: phase("Serial adapter disconnected. Waiting for interface", head=1, offset=len('Found')) search_announced = True else: if self.settings.built_in_serial_interface in sorted(os.listdir('/dev/')): return f'/dev/{self.settings.built_in_serial_interface}' raise CriticalError(f"Error: /dev/{self.settings.built_in_serial_interface} was not found.")
def get_b58_key(k_type: str) -> bytes: """Ask user to input Base58 encoded public key from RxM.""" if k_type == 'pubkey': clear_screen() c_print("Import public key from RxM", head=1, tail=1) c_print("WARNING") message_printer( "Key exchange will break the HW separation. " "Outside specific requests TxM (this computer) " "makes, you must never copy any data from " "NH/RxM to TxM. Doing so could infect TxM, that " "could then later covertly transmit private " "keys/messages to adversary on NH.", head=1, tail=1) box_msg = "Enter contact's public key from RxM" elif k_type == 'localkey': box_msg = "Enter local key decryption key from TxM" elif k_type == 'imported_file': box_msg = "Enter file decryption key" else: raise CriticalError("Invalid key type") while True: pub_key = box_input(box_msg, expected_len=59) pub_key = ''.join(pub_key.split()) try: return b58decode(pub_key) except ValueError: c_print("Checksum error - Check that entered key is correct.", head=1) print_on_previous_line(reps=4, delay=1.5)
def search_serial_interface(self) -> str: """Search for serial interface.""" if self.settings.session_usb_iface: search_announced = False if not self.init_found: print_on_previous_line() phase("Searching for USB-to-serial interface") while True: time.sleep(0.1) for f in sorted(os.listdir('/dev')): if f.startswith('ttyUSB'): if self.init_found: time.sleep(1.5) phase('Found', done=True) if self.init_found: print_on_previous_line(reps=2) self.init_found = True return '/dev/{}'.format(f) else: if not search_announced: if self.init_found: phase( "Serial adapter disconnected. Waiting for interface", head=1) search_announced = True else: f = 'serial0' if 'Raspbian' in platform.platform() else 'ttyS0' if f in sorted(os.listdir('/dev/')): return '/dev/{}'.format(f) else: raise CriticalError("Error: /dev/{} was not found.".format(f))
def deliver_local_key(local_key_packet: bytes, kek: bytes, c_code: bytes, settings: 'Settings', queues: 'QueueDict') -> None: """Deliver encrypted local key to Destination Computer.""" nc_bypass_msg(NC_BYPASS_START, settings) queue_to_nc(local_key_packet, queues[RELAY_PACKET_QUEUE]) while True: print_key("Local key decryption key (to Receiver)", kek, settings) purp_code = ask_confirmation_code("Receiver") if purp_code == c_code.hex(): nc_bypass_msg(NC_BYPASS_STOP, settings) break elif purp_code == "": phase("Resending local key", head=2) queue_to_nc(local_key_packet, queues[RELAY_PACKET_QUEUE]) phase(DONE) print_on_previous_line( reps=(9 if settings.local_testing_mode else 10)) else: m_print([ "Incorrect confirmation code. If Receiver did not receive", "the encrypted local key, resend it by pressing <Enter>." ], head=1) print_on_previous_line( reps=(9 if settings.local_testing_mode else 10), delay=2)
def deliver_onion_service_data(relay_command: bytes, onion_service: 'OnionService', gateway: 'Gateway') -> None: """Send Onion Service data to Replay Program on Networked Computer.""" gateway.write(relay_command) while True: purp_code = ask_confirmation_code('Relay') if purp_code == onion_service.conf_code.hex(): onion_service.is_delivered = True onion_service.new_confirmation_code() break if purp_code == '': phase("Resending Onion Service data", head=2) gateway.write(relay_command) phase(DONE) print_on_previous_line(reps=5) else: m_print([ "Incorrect confirmation code. If Relay Program did not", "receive Onion Service data, resend it by pressing <Enter>." ], head=1) print_on_previous_line(reps=5, delay=2)
def deliver_contact_data( header: bytes, # Key type (x448, PSK) nick: str, # Contact's nickname onion_pub_key: bytes, # Public key of contact's v3 Onion Service tx_mk: bytes, # Message key for outgoing messages rx_mk: bytes, # Message key for incoming messages tx_hk: bytes, # Header key for outgoing messages rx_hk: bytes, # Header key for incoming messages queues: 'QueueDict', # Dictionary of multiprocessing queues settings: 'Settings', # Settings object ) -> None: """Deliver contact data to Destination Computer.""" c_code = blake2b(onion_pub_key, digest_size=CONFIRM_CODE_LENGTH) command = (header + onion_pub_key + tx_mk + rx_mk + tx_hk + rx_hk + str_to_bytes(nick)) queue_command(command, settings, queues) while True: purp_code = ask_confirmation_code("Receiver") if purp_code == c_code.hex(): break elif purp_code == "": phase("Resending contact data", head=2) queue_command(command, settings, queues) phase(DONE) print_on_previous_line(reps=5) else: m_print("Incorrect confirmation code.", head=1) print_on_previous_line(reps=4, delay=2)
def rxp_load_psk(window: 'TxWindow', contact_list: 'ContactList', settings: 'Settings', queues: 'QueueDict', ) -> None: """Send command to Receiver Program to load PSK for active contact.""" if settings.traffic_masking: raise SoftError("Error: Command is disabled during traffic masking.", head_clear=True) if window.type == WIN_TYPE_GROUP or window.contact is None: raise SoftError("Error: Group is selected.", head_clear=True) if not contact_list.get_contact_by_pub_key(window.uid).uses_psk(): raise SoftError(f"Error: The current key was exchanged with {ECDHE}.", head_clear=True) c_code = blake2b(window.uid, digest_size=CONFIRM_CODE_LENGTH) command = KEY_EX_PSK_RX + c_code + window.uid queue_command(command, settings, queues) while True: try: purp_code = ask_confirmation_code('Receiver') if purp_code == c_code.hex(): window.contact.kex_status = KEX_STATUS_HAS_RX_PSK contact_list.store_contacts() raise SoftError(f"Removed PSK reminder for {window.name}.", tail_clear=True, delay=1) m_print("Incorrect confirmation code.", head=1) print_on_previous_line(reps=4, delay=2) except (EOFError, KeyboardInterrupt): raise SoftError("PSK install verification aborted.", tail_clear=True, delay=1, head=2)
def load_master_key(self) -> None: """Derive master key from password from stored values (salt, rounds, memory).""" ensure_dir(f'{DIR_USER_DATA}/') with open(self.file_name, 'rb') as f: data = f.read() salt = data[0:32] k_hash = data[32:64] rounds = bytes_to_int(data[64:72]) memory = bytes_to_int(data[72:80]) while True: password = MasterKey.get_password() phase("Deriving master key", head=2, offset=16) purp_key, _ = argon2_kdf(password, salt, rounds, memory, local_testing=self.local_test) if hash_chain(purp_key) == k_hash: phase("Password correct", done=True) self.master_key = purp_key clear_screen(delay=0.5) break else: phase("Invalid password", done=True) print_on_previous_line(reps=5, delay=1)
def load_master_key(self) -> bytes: """Derive the master key from password and salt. Load the salt, hash, and key derivation settings from the login database. Derive the purported master key from the salt and entered password. If the BLAKE2b hash of derived master key matches the hash in the login database, accept the derived master key. """ database_data = self.database.load_database() if len(database_data) != MASTERKEY_DB_SIZE: raise CriticalError(f"Invalid {self.file_name} database size.") salt, key_hash, time_bytes, memory_bytes, parallelism_bytes \ = separate_headers(database_data, [ARGON2_SALT_LENGTH, BLAKE2_DIGEST_LENGTH, ENCODED_INTEGER_LENGTH, ENCODED_INTEGER_LENGTH]) time_cost = bytes_to_int(time_bytes) memory_cost = bytes_to_int(memory_bytes) parallelism = bytes_to_int(parallelism_bytes) while True: password = MasterKey.get_password() phase("Deriving master key", head=2, offset=len("Password correct")) purp_key = argon2_kdf(password, salt, time_cost, memory_cost, parallelism) if blake2b(purp_key) == key_hash: phase("Password correct", done=True, delay=1) clear_screen() return purp_key phase("Invalid password", done=True, delay=1) print_on_previous_line(reps=5)
def get_onion_address_from_user(onion_address_user: str, queues: 'QueueDict') -> str: """Get contact's Onion Address from user.""" while True: onion_address_contact = box_input("Contact account", expected_len=ONION_ADDRESS_LENGTH) error_msg = validate_onion_addr(onion_address_contact, onion_address_user) if error_msg: m_print(error_msg, head=1) print_on_previous_line(reps=5, delay=1) if error_msg not in [ "Error: Invalid account length.", "Error: Account must be in lower case.", "Error: Can not add reserved account.", "Error: Can not add own account." ]: relay_command = UNENCRYPTED_DATAGRAM_HEADER + UNENCRYPTED_ACCOUNT_CHECK + onion_address_contact.encode( ) queue_to_nc(relay_command, queues[RELAY_PACKET_QUEUE]) continue return onion_address_contact
def get_b58_key(key_type: str, # The type of Base58 key to be entered settings: 'Settings', # Settings object short_address: str = '' # The contact's short Onion address ) -> bytes: # The Base58 decoded key """Ask the user to input a Base58 encoded key.""" if key_type == B58_PUBLIC_KEY: clear_screen() m_print(f"{ECDHE} key exchange", head=1, tail=1, bold=True) m_print("If needed, resend your public key to the contact by pressing <Enter>", tail=1) box_msg = f"Enter public key of {short_address} (from Relay)" elif key_type == B58_LOCAL_KEY: box_msg = "Enter local key decryption key (from Transmitter)" else: raise CriticalError("Invalid key type") while True: rx_pk = box_input(box_msg, key_type=key_type, guide=not (settings.local_testing_mode or settings.qubes)) rx_pk = ''.join(rx_pk.split()) if key_type == B58_PUBLIC_KEY and rx_pk == '': return rx_pk.encode() try: return b58decode(rx_pk, public_key=(key_type == B58_PUBLIC_KEY)) except ValueError: m_print("Checksum error - Check that the entered key is correct.") print_on_previous_line(reps=(4 if settings.local_testing_mode else 5), delay=1) if key_type == B58_PUBLIC_KEY and len(rx_pk) == ENCODED_B58_PUB_KEY_LENGTH: raise ValueError(rx_pk)
def check_kernel_entropy() -> None: """Wait until the kernel CSPRNG is sufficiently seeded. Wait until the `entropy_avail` file states that kernel entropy pool has at least 512 bits of entropy. The waiting ensures the ChaCha20 CSPRNG is fully seeded (i.e., it has the maximum of 384 bits of entropy) when it generates keys. The same entropy threshold is used by the GETRANDOM syscall in random.c: #define CRNG_INIT_CNT_THRESH (2*CHACHA20_KEY_SIZE) For more information on the kernel CSPRNG threshold, see https://security.stackexchange.com/a/175771/123524 https://crypto.stackexchange.com/a/56377 """ message = "Waiting for kernel CSPRNG entropy pool to fill up" phase(message, head=1) ent_avail = 0 while ent_avail < ENTROPY_THRESHOLD: with ignored(EOFError, KeyboardInterrupt): with open('/proc/sys/kernel/random/entropy_avail') as f: ent_avail = int(f.read().strip()) m_print(f"{ent_avail}/{ENTROPY_THRESHOLD}") print_on_previous_line(delay=0.1) print_on_previous_line() phase(message) phase(DONE)
def win_activity(window_list: 'WindowList') -> None: """Show number of unread messages in each window.""" unread_wins = [w for w in window_list if (w.uid != WIN_UID_COMMAND and w.unread_messages > 0)] print_list = ["Window activity"] if unread_wins else ["No window activity"] print_list += [f"{w.name}: {w.unread_messages}" for w in unread_wins] m_print(print_list, box=True) print_on_previous_line(reps=(len(print_list) + 2), delay=1)
def box_input(title: str, default: str = '', head: int = 0, tail: int = 0, expected_len: int = 0, validator: Callable = None, validator_args: Any = None) -> str: """Display boxed prompt for user with title. :param title: Title for data to prompt :param default: Default return value :param head: Number of new lines to print before input :param tail: Number of new lines to print after input :param expected_len Expected length of input :param validator: Input validator function :param validator_args: Arguments required by the validator :return: Input from user """ for _ in range(head): print('') tty_w = get_tty_w() input_len = tty_w - 2 if expected_len == 0 else expected_len + 2 input_top_line = '┌' + input_len * '─' + '┐' input_line = '│' + input_len * ' ' + '│' input_bot_line = '└' + input_len * '─' + '┘' input_line_indent = (tty_w - len(input_line)) // 2 input_box_indent = input_line_indent * ' ' print(input_box_indent + input_top_line) print(input_box_indent + input_line) print(input_box_indent + input_bot_line) print(4 * CURSOR_UP_ONE_LINE) print(input_box_indent + '┌─┤' + title + '├') user_input = input(input_box_indent + '│ ') if user_input == '': print(2 * CURSOR_UP_ONE_LINE) print(input_box_indent + f'│ {default}') user_input = default if validator is not None: success, error_msg = validator(user_input, validator_args) if not success: c_print("Error: {}".format(error_msg), head=1) print_on_previous_line(reps=4, delay=1.5) return box_input(title, default, head, tail, expected_len, validator, validator_args) for _ in range(tail): print('') return user_input
def show_win_activity(window_list: 'WindowList') -> None: """Show number of unread messages in each window.""" unread_wins = [ w for w in window_list if (w.uid != 'local' and w.unread_messages > 0) ] print_list = ["Window activity"] if unread_wins else ["No window activity"] for w in unread_wins: print_list.append(f"{w.name}: {w.unread_messages}") box_print(print_list) print_on_previous_line(reps=(len(print_list) + 2), delay=1.5)
def check_empty_group(self) -> None: """Notify the user if group was empty.""" if self.type == 'message' and self.window.type == 'group' and len( self.window.window_contacts) == 0: print_on_previous_line() print( f"Msg to {self.w_type}{self.window.name}: Error: Group is empty." ) print_on_previous_line(delay=0.5) self.__init__(self.window, self.settings)
def print(self, msg_tuple: MsgTuple, file: Any = None) -> None: """Print a new message to the window.""" # Unpack tuple ts, message, onion_pub_key, origin, whisper, event_msg = msg_tuple # Determine handle handle = self.get_handle(ts, onion_pub_key, origin, whisper, event_msg) # Check if message content needs to be changed to privacy-preserving notification if not self.is_active and not self.settings.new_message_notify_preview and self.uid != WIN_UID_COMMAND: trailer = 's' if self.unread_messages > 0 else '' message = BOLD_ON + f"{self.unread_messages + 1} unread message{trailer}" + NORMAL_TEXT # Wrap message wrapper = textwrap.TextWrapper(width=get_terminal_width(), initial_indent=handle, subsequent_indent=len(handle) * ' ') wrapped = wrapper.fill(message) if wrapped == '': wrapped = handle # Add bolding unless export file is provided bold_on, bold_off, f_name = (BOLD_ON, NORMAL_TEXT, sys.stdout) if file is None else ('', '', file) wrapped = bold_on + wrapped[:len(handle )] + bold_off + wrapped[len(handle):] if self.is_active: if self.previous_msg_ts.date() != ts.date(): print(bold_on + f"00:00 -!- Day changed to {str(ts.date())}" + bold_off, file=f_name) print(wrapped, file=f_name) else: if onion_pub_key != WIN_UID_COMMAND: self.unread_messages += 1 if (self.type == WIN_TYPE_CONTACT and self.contact is not None and self.contact.notifications) \ or (self.type == WIN_TYPE_GROUP and self.group is not None and self.group.notifications) \ or (self.type == WIN_TYPE_COMMAND): lines = wrapped.split('\n') if len(lines) > 1: print(lines[0][:-1] + '…') # Preview only first line of the long message else: print(wrapped) print_on_previous_line( delay=self.settings.new_message_notify_duration, flush=True) self.previous_msg_ts = ts
def new_password(cls, purpose: str = "master password") -> str: """Prompt the user to enter and confirm a new password.""" password_1 = pwd_prompt(f"Enter a new {purpose}: ") password_2 = pwd_prompt(f"Confirm the {purpose}: ", repeat=True) if password_1 == password_2: return password_1 else: m_print("Error: Passwords did not match. Try again.", head=1, tail=1) print_on_previous_line(delay=1, reps=7) return cls.new_password(purpose)
def rxm_show_cmd_win(window: 'Window', settings: 'Settings', c_queue: 'Queue') -> None: """Show command window on RxM until user presses Enter.""" packet = WINDOW_CHANGE_HEADER + LOCAL_WIN_ID_BYTES queue_command(packet, settings, c_queue) box_print(f"<Enter> returns RxM to {window.name}'s window", manual_proceed=True) print_on_previous_line(reps=4, flush=True) packet = WINDOW_CHANGE_HEADER + window.uid.encode() queue_command(packet, settings, c_queue)
def rxm_display_f_win(window: 'Window', settings: 'Settings', c_queue: 'Queue'): """Show file reception window on RxM until user presses Enter.""" packet = WINDOW_CHANGE_HEADER + FILE_R_WIN_ID_BYTES queue_command(packet, settings, c_queue) box_print(f"<Enter> returns RxM to {window.name}'s window", manual_proceed=True) print_on_previous_line(reps=4, flush=True) packet = WINDOW_CHANGE_HEADER + window.uid.encode() queue_command(packet, settings, c_queue)
def yes( prompt: str, # Question to be asked abort: Optional[bool] = None, # Determines the return value of ^C and ^D head: int = 0, # Number of new lines to print before prompt tail: int = 0 # Number of new lines to print after prompt ) -> bool: # True/False depending on input """Prompt the user a question that is answered with yes/no.""" print_spacing(head) prompt = f"{prompt} (y/n): " input_space = len(' yes ') upper_line = '┌' + (len(prompt) + input_space) * '─' + '┐' title_line = '│' + prompt + input_space * ' ' + '│' lower_line = '└' + (len(prompt) + input_space) * '─' + '┘' terminal_w = get_terminal_width() upper_line = upper_line.center(terminal_w) title_line = title_line.center(terminal_w) lower_line = lower_line.center(terminal_w) indent = title_line.find('│') terminal_width_check(len(upper_line)) print(upper_line) while True: print(title_line) print(lower_line) print(3 * CURSOR_UP_ONE_LINE) try: user_input = input(indent * ' ' + f'│ {prompt}') except (EOFError, KeyboardInterrupt): if abort is None: raise print('') user_input = 'y' if abort else 'n' print_on_previous_line() if user_input == '': continue if user_input.lower() in ['y', 'yes']: print(indent * ' ' + f'│ {prompt}Yes │\n') print_spacing(tail) return True if user_input.lower() in ['n', 'no']: print(indent * ' ' + f'│ {prompt}No │\n') print_spacing(tail) return False
def yes(prompt: str, head: int = 0, tail: int = 0) -> bool: """Prompt user a question that is answered with yes / no. :param prompt: Question to be asked :param head: Number of new lines to print before prompt :param tail: Number of new lines to print after prompt :return: True if user types 'y' or 'yes' False if user types 'n' or 'no' """ for _ in range(head): print('') prompt = "{} (y/n): ".format(prompt) tty_w = get_tty_w() upper_line = ('┌' + (len(prompt) + 5) * '─' + '┐') title_line = ('│' + prompt + 5 * ' ' + '│') lower_line = ('└' + (len(prompt) + 5) * '─' + '┘') upper_line = upper_line.center(tty_w) title_line = title_line.center(tty_w) lower_line = lower_line.center(tty_w) indent = title_line.find('│') print(upper_line) print(title_line) print(lower_line) print(3 * CURSOR_UP_ONE_LINE) while True: print(title_line) print(lower_line) print(3 * CURSOR_UP_ONE_LINE) answer = input(indent * ' ' + f'│ {prompt}') print_on_previous_line() if answer == '': continue if answer.lower() in 'yes': print(indent * ' ' + f'│ {prompt}Yes │\n') for _ in range(tail): print('') return True elif answer.lower() in 'no': print(indent * ' ' + f'│ {prompt}No │\n') for _ in range(tail): print('') return False else: continue
def process_aliases(self) -> None: """Check if input was an alias for existing command.""" aliases = [(' ', '/unread'), (' ', '/exit' if self.settings.double_space_exits else '/clear'), ('//', '/cmd')] for a in aliases: if self.plaintext == a[0]: self.plaintext = a[1] print_on_previous_line() print( f"Msg to {self.w_type}{self.window.name}: {self.plaintext}" )
def new_password(cls, purpose: str = "master password") -> str: """Prompt user to enter and confirm a new password.""" password_1 = pwd_prompt(f"Enter a new {purpose}: ", '┌', '┐') password_2 = pwd_prompt(f"Confirm the {purpose}: ", '├', '┤') if password_1 == password_2: return password_1 else: c_print("Error: Passwords did not match. Try again.", head=1, tail=1) time.sleep(1) print_on_previous_line(reps=7) return cls.new_password(purpose)
def determine_memory_cost( self, password: str, salt: bytes, time_cost: int, memory_cost: int, parallelism: int, ) -> Tuple[int, bytes]: """Determine suitable memory_cost value for Argon2id. If we reached this function, it means we found a `t+1` value for time_cost (explained in the `determine_time_cost` function). We therefore do a binary search on the amount of memory to use until we hit the desired key derivation time range. """ lower_bound = ARGON2_MIN_MEMORY_COST upper_bound = memory_cost while True: memory_cost = int(round((lower_bound + upper_bound) // 2, -3)) print_on_previous_line() phase(f"Trying memory cost {memory_cost} KiB") master_key, kd_time = self.timed_key_derivation( password, salt, time_cost, memory_cost, parallelism) phase(f"{kd_time:.1f}s", done=True) # If we found a suitable memory_cost value, we accept the key and the memory_cost. if MIN_KEY_DERIVATION_TIME <= kd_time <= MAX_KEY_DERIVATION_TIME: return memory_cost, master_key # The search might fail e.g. if external CPU load causes delay in key # derivation, which causes the search to continue into wrong branch. In # such a situation the search is restarted. The binary search is problematic # with tight key derivation time target ranges, so if the search keeps # restarting, increasing MAX_KEY_DERIVATION_TIME (and thus expanding the # range) will help finding suitable memory_cost value faster. Increasing # MAX_KEY_DERIVATION_TIME slightly affects security (positively) and user # experience (negatively). if memory_cost == lower_bound or memory_cost == upper_bound: lower_bound = ARGON2_MIN_MEMORY_COST upper_bound = self.get_available_memory() continue if kd_time < MIN_KEY_DERIVATION_TIME: lower_bound = memory_cost elif kd_time > MAX_KEY_DERIVATION_TIME: upper_bound = memory_cost