def export_logs(user_input: 'UserInput', window: 'Window', contact_list: 'ContactList', settings: 'Settings', c_queue: 'Queue', master_key: 'MasterKey') -> None: """Export log files to plaintext file on TxM/RxM. TxM only exports sent messages, RxM exports full conversation. """ try: no_messages_str = user_input.plaintext.split()[1] if not no_messages_str.isdigit(): raise FunctionReturn("Specified invalid number of messages to export.") no_messages = int(no_messages_str) except IndexError: no_messages = 0 if not yes(f"Export logs for {window.name} in plaintext?", head=1, tail=1): raise FunctionReturn("Logfile export aborted.") packet = LOG_EXPORT_HEADER + window.uid.encode() + US_BYTE + int_to_bytes(no_messages) queue_command(packet, settings, c_queue) access_history(window, contact_list, settings, master_key, no_messages, export=True)
def group_rm_group(group_name: str, group_list: 'GroupList', settings: 'Settings', queues: Dict[bytes, 'Queue'], master_key: 'MasterKey'): """Remove group with it's members.""" if not yes(f"Remove group '{group_name}'?", head=1): raise FunctionReturn("Group removal aborted.") rm_logs = yes("Also remove logs for the group?", head=1) command = GROUP_DELETE_HEADER + group_name.encode() queue_command(command, settings, queues[COMMAND_PACKET_QUEUE]) if rm_logs: command = LOG_REMOVE_HEADER + group_name.encode() queue_command(command, settings, queues[COMMAND_PACKET_QUEUE]) with ignored(FunctionReturn): remove_logs(group_name, settings, master_key) if group_name not in group_list.get_list_of_group_names(): raise FunctionReturn(f"TxM has no group '{group_name}' to remove.") group = group_list.get_group(group_name) if group.has_members() and yes("Notify members about leaving the group?"): for member in group: queue_message(user_input=UserInput(group_name, MESSAGE), window =MockWindow(member.rx_account, [member]), settings =settings, m_queue =queues[MESSAGE_PACKET_QUEUE], header =GROUP_MSG_EXIT_GROUP_HEADER, log_as_ph =True) group_list.remove_group(group_name) raise FunctionReturn(f"Removed group '{group_name}'")
def test_normal(self): # Setup settings = Settings() c_queue = Queue() # Verify short commands self.assertIsNone(queue_command(os.urandom(200), settings, c_queue)) c_pt, settings_ = c_queue.get() self.assertEqual(len(c_pt), 256) self.assertIsInstance(settings_, Settings) # Verify long commands self.assertIsNone(queue_command(os.urandom(255), settings, c_queue)) # Long commands are split to multiple queue items. self.assertEqual(c_queue.qsize(), 2) while not c_queue.empty(): c_pt, settings_ = c_queue.get() self.assertEqual(len(c_pt), 256) self.assertIsInstance(settings_, Settings) # Teardown time.sleep(0.2) c_queue.close()
def wipe(settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None: """Reset terminals, wipe all user data from TxM/RxM/NH and power off systems. No effective RAM overwriting tool currently exists, so as long as TxM/RxM use FDE and DDR3 memory, recovery of user data becomes impossible very fast: https://www1.cs.fau.de/filepool/projects/coldboot/fares_coldboot.pdf """ if not yes("Wipe all user data and power off systems?"): raise FunctionReturn("Wipe command aborted.") clear_screen() for q in [COMMAND_PACKET_QUEUE, NH_PACKET_QUEUE]: while queues[q].qsize() != 0: queues[q].get() queue_command(WIPE_USER_DATA_HEADER, settings, queues[COMMAND_PACKET_QUEUE]) if not settings.session_traffic_masking: if settings.local_testing_mode: time.sleep(0.8) if settings.data_diode_sockets: time.sleep(2.2) else: time.sleep(settings.race_condition_delay) queue_to_nh(UNENCRYPTED_PACKET_HEADER + UNENCRYPTED_WIPE_COMMAND, settings, queues[NH_PACKET_QUEUE]) os.system('reset')
def change_nick(user_input: 'UserInput', window: 'Window', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', c_queue: 'Queue') -> None: """Change nick of contact.""" if window.type == 'group': raise FunctionReturn("Error: Group is selected.") try: nick = user_input.plaintext.split()[1] except IndexError: raise FunctionReturn("Error: No nick specified.") rx_acco = window.contact.rx_account success, error_msg = validate_nick(nick, (contact_list, group_list, rx_acco)) if not success: raise FunctionReturn(error_msg) window.contact.nick = nick window.name = nick contact_list.store_contacts() packet = CHANGE_NICK_HEADER + rx_acco.encode() + US_BYTE + nick.encode() queue_command(packet, settings, c_queue) box_print(f"Changed {rx_acco} nick to {nick}.")
def change_setting(user_input: 'UserInput', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None: """Change setting on TxM / RxM.""" try: setting = user_input.plaintext.split()[1] except IndexError: raise FunctionReturn("Error: No setting specified.") if setting not in settings.key_list: raise FunctionReturn(f"Error: Invalid setting '{setting}'") try: value = user_input.plaintext.split()[2] except IndexError: raise FunctionReturn("Error: No value for setting specified.") pt_cmd = dict(serial_error_correction=UNENCRYPTED_EC_RATIO, serial_baudrate=UNENCRYPTED_BAUDRATE, disable_gui_dialog=UNENCRYPTED_GUI_DIALOG) if setting in pt_cmd: if settings.session_traffic_masking: raise FunctionReturn( "Error: Can't change this setting during traffic masking.") settings.change_setting(setting, value, contact_list, group_list) command = CHANGE_SETTING_HEADER + setting.encode( ) + US_BYTE + value.encode() queue_command(command, settings, queues[COMMAND_PACKET_QUEUE]) if setting in pt_cmd: packet = UNENCRYPTED_PACKET_HEADER + pt_cmd[setting] + value.encode() queue_to_nh(packet, settings, queues[NH_PACKET_QUEUE])
def log_command(user_input: 'UserInput', window: 'TxWindow', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', c_queue: 'Queue', master_key: 'MasterKey') -> None: """Display message logs or export them to plaintext file on TxM/RxM. TxM processes sent messages, RxM processes sent and received messages for all participants in active window. """ cmd = user_input.plaintext.split()[0] export, header = dict(export=(True, LOG_EXPORT_HEADER), history=(False, LOG_DISPLAY_HEADER))[cmd] try: msg_to_load = int(user_input.plaintext.split()[1]) except ValueError: raise FunctionReturn("Error: Invalid number of messages.") except IndexError: msg_to_load = 0 if export and not yes( f"Export logs for '{window.name}' in plaintext?", head=1, tail=1): raise FunctionReturn("Logfile export aborted.") try: command = header + window.uid.encode() + US_BYTE + int_to_bytes( msg_to_load) except struct.error: raise FunctionReturn("Error: Invalid number of messages.") queue_command(command, settings, c_queue) access_logs(window, contact_list, group_list, settings, master_key, msg_to_load, export)
def clear_screens(user_input: 'UserInput', window: 'TxWindow', settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None: """Clear/reset TxM, RxM and NH screens. Only send unencrypted command to NH if traffic masking is disabled and if some related IM account can be bound to active window. Since reset command removes ephemeral message log on RxM, TxM decides the window to reset (in case e.g. previous window selection command packet dropped and active window state is inconsistent between TxM/RxM). """ cmd = user_input.plaintext.split()[0] command = CLEAR_SCREEN_HEADER if cmd == CLEAR else RESET_SCREEN_HEADER + window.uid.encode( ) queue_command(command, settings, queues[COMMAND_PACKET_QUEUE]) clear_screen() if not settings.session_traffic_masking and window.imc_name is not None: im_window = window.imc_name.encode() pt_cmd = UNENCRYPTED_SCREEN_CLEAR if cmd == CLEAR else UNENCRYPTED_SCREEN_RESET packet = UNENCRYPTED_PACKET_HEADER + pt_cmd + im_window queue_to_nh(packet, settings, queues[NH_PACKET_QUEUE]) if cmd == RESET: os.system('reset')
def group_add_member(group_name: str, purp_members: List['str'], group_list: 'GroupList', contact_list: 'ContactList', settings: 'Settings', queues: Dict[bytes, 'Queue'], master_key: 'MasterKey') -> None: """Add new member(s) to group.""" if group_name not in group_list.get_list_of_group_names(): if yes(f"Group {group_name} was not found. Create new group?", head=1): group_create(group_name, purp_members, group_list, contact_list, settings, queues, master_key) return None else: raise FunctionReturn("Group creation aborted.") purp_accounts = set(purp_members) accounts = set(contact_list.get_list_of_accounts()) before_adding = set(group_list.get_group(group_name).get_list_of_member_accounts()) ok_accounts_set = set(accounts & purp_accounts) new_in_group_set = set(ok_accounts_set - before_adding) end_assembly = list(before_adding | new_in_group_set) rejected = list(purp_accounts - accounts) already_in_g = list(before_adding & purp_accounts) new_in_group = list(new_in_group_set) ok_accounts = list(ok_accounts_set) if len(end_assembly) > settings.max_number_of_group_members: raise FunctionReturn(f"Error: TFC settings only allow {settings.max_number_of_group_members} members per group.") group = group_list.get_group(group_name) group.add_members([contact_list.get_contact(a) for a in new_in_group]) fields = [f.encode() for f in ([group_name] + ok_accounts)] command = GROUP_ADD_HEADER + US_BYTE.join(fields) queue_command(command, settings, queues[COMMAND_PACKET_QUEUE]) group_management_print(ADDED_MEMBERS, new_in_group, contact_list, group_name) group_management_print(ALREADY_MEMBER, already_in_g, contact_list, group_name) group_management_print(UNKNOWN_ACCOUNTS, rejected, contact_list, group_name) if new_in_group: if yes("Publish new list of members to involved?"): for member in before_adding: queue_message(user_input=UserInput(US_STR.join([group_name] + new_in_group), MESSAGE), window =MockWindow(member, [contact_list.get_contact(member)]), settings =settings, m_queue =queues[MESSAGE_PACKET_QUEUE], header =GROUP_MSG_MEMBER_ADD_HEADER, log_as_ph =True) for member in new_in_group: m_list = [m for m in end_assembly if m != member] queue_message(user_input=UserInput(US_STR.join([group_name] + m_list), MESSAGE), window =MockWindow(member, [contact_list.get_contact(member)]), settings =settings, m_queue =queues[MESSAGE_PACKET_QUEUE], header =GROUP_MSG_INVITEJOIN_HEADER, log_as_ph =True)
def remove_contact(user_input: 'UserInput', window: 'TxWindow', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', queues: Dict[bytes, 'Queue'], master_key: 'MasterKey') -> None: """Remove contact on TxM/RxM.""" if settings.session_traffic_masking: raise FunctionReturn( "Error: Command is disabled during traffic masking.") try: selection = user_input.plaintext.split()[1] except IndexError: raise FunctionReturn("Error: No account specified.") if not yes(f"Remove {selection} completely?", head=1): raise FunctionReturn("Removal of contact aborted.") rm_logs = yes(f"Also remove logs for {selection}?", head=1) # Load account if selector was nick if selection in contact_list.get_list_of_nicks(): selection = contact_list.get_contact(selection).rx_account packet = CONTACT_REMOVE_HEADER + selection.encode() queue_command(packet, settings, queues[COMMAND_PACKET_QUEUE]) if rm_logs: packet = LOG_REMOVE_HEADER + selection.encode() queue_command(packet, settings, queues[COMMAND_PACKET_QUEUE]) with ignored(FunctionReturn): remove_logs(selection, settings, master_key) queues[KEY_MANAGEMENT_QUEUE].put((KDB_REMOVE_ENTRY_HEADER, selection)) if selection in contact_list.get_list_of_accounts(): contact_list.remove_contact(selection) box_print(f"Removed {selection} from contacts.", head=1, tail=1) else: box_print(f"TxM has no {selection} to remove.", head=1, tail=1) if any([g.remove_members([selection]) for g in group_list]): box_print(f"Removed {selection} from group(s).", tail=1) if window.type == WIN_TYPE_CONTACT: if selection == window.uid: window.deselect_window() if window.type == WIN_TYPE_GROUP: for c in window: if selection == c.rx_account: window.update_group_win_members(group_list) # If last member from group is removed, deselect group. # Deselection is not done in update_group_win_members # because it would prevent selecting the empty group # for group related commands such as notifications. if not window.window_contacts: window.deselect_window()
def new_psk(account: str, user: str, nick: str, contact_list: 'ContactList', settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None: """Generate new pre-shared key for manual key delivery. :param account: The contact's account name (e.g. [email protected]) :param user: The user's account name (e.g. [email protected]) :param nick: Nick of contact :param contact_list: Contact list object :param settings: Settings object :param queues: Dictionary of multiprocessing queues :return: None """ try: tx_key = keygen() tx_hek = keygen() salt = keygen() password = MasterKey.new_password("password for PSK") phase("Deriving key encryption key", head=2) kek, _ = argon2_kdf(password, salt, rounds=16, memory=128000, parallelism=1) phase('Done') ct_tag = encrypt_and_sign(tx_key + tx_hek, key=kek) store_d = ask_path_gui(f"Select removable media for {nick}", settings) f_name = f"{store_d}/{user}.psk - Give to {account}" try: with open(f_name, 'wb+') as f: f.write(salt + ct_tag) except PermissionError: raise FunctionReturn( "Error: Did not have permission to write to directory.") packet = KEY_EX_PSK_TX_HEADER \ + tx_key \ + tx_hek \ + account.encode() + US_BYTE + nick.encode() queue_command(packet, settings, queues[COMMAND_PACKET_QUEUE]) contact_list.add_contact(account, user, nick, bytes(32), bytes(32), settings.log_msg_by_default, settings.store_file_default, settings.n_m_notify_privacy) queues[KEY_MANAGEMENT_QUEUE].put( ('ADD', account, tx_key, bytes(32), tx_hek, bytes(32))) box_print([f"Successfully added {nick}."], head=1) clear_screen(delay=1) except KeyboardInterrupt: raise FunctionReturn("PSK generation aborted.")
def contact_setting(user_input: 'UserInput', window: 'Window', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', c_queue: 'Queue') -> None: """Change logging, file reception, or message notification setting of (all) contact(s).""" try: parameters = user_input.plaintext.split() cmd_key = parameters[0] cmd_header = dict(logging=CHANGE_LOGGING_HEADER, store=CHANGE_FILE_R_HEADER, notify=CHANGE_NOTIFY_HEADER)[cmd_key] s_value = dict(on=b'e', off=b'd')[parameters[1]] b_value = dict(on=True, off=False)[parameters[1]] except (IndexError, KeyError): raise FunctionReturn("Error: Invalid command.") # If second parameter 'all' is included, apply setting for all contacts and groups try: target = b'' if parameters[2] == 'all': cmd_value = s_value.upper() else: raise FunctionReturn("Error: Invalid command.") except IndexError: target = window.uid.encode() cmd_value = s_value + US_BYTE + target if target: if window.type == 'contact': if cmd_key == 'logging': window.contact.log_messages = b_value if cmd_key == 'store': window.contact.file_reception = b_value if cmd_key == 'notify': window.contact.notifications = b_value contact_list.store_contacts() if window.type == 'group': if cmd_key == 'logging': window.group.log_messages = b_value if cmd_key == 'store': for c in window: c.file_reception = b_value if cmd_key == 'notify': window.group.notifications = b_value group_list.store_groups() else: for contact in contact_list: if cmd_key == 'logging': contact.log_messages = b_value if cmd_key == 'store': contact.file_reception = b_value if cmd_key == 'notify': contact.notifications = b_value contact_list.store_contacts() for group in group_list: if cmd_key == 'logging': group.log_messages = b_value if cmd_key == 'notify': group.notifications = b_value group_list.store_groups() packet = cmd_header + cmd_value queue_command(packet, settings, c_queue)
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 clear_screens(window: 'Window', settings: 'Settings', c_queue: 'Queue', gateway: 'Gateway') -> None: """Clear TxM, RxM and NH screens.""" clear_screen() queue_command(CLEAR_SCREEN_HEADER, settings, c_queue) if not settings.session_trickle: if window.imc_name is not None: im_window = window.imc_name.encode() time.sleep(0.5) transmit(UNENCRYPTED_PACKET_HEADER + UNENCRYPTED_SCREEN_CLEAR + im_window, settings, gateway)
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 select_tx_window(self, settings: 'Settings', queues: Dict[bytes, 'Queue'], selection: str = None, cmd: bool = False) -> None: """Select specified window or ask the user to specify one.""" if selection is None: self.contact_list.print_contacts() self.group_list.print_groups() selection = input("Select recipient: ").strip() if selection in self.group_list.get_list_of_group_names(): if cmd and settings.session_traffic_masking and selection != self.uid: raise FunctionReturn("Error: Can't change window during traffic masking.") self.group = self.group_list.get_group(selection) self.window_contacts = self.group.members self.name = self.group.name self.uid = self.name self.log_messages = self.group.log_messages self.type = WIN_TYPE_GROUP self.type_print = 'group' if self.window_contacts: self.imc_name = self.window_contacts[0].rx_account elif selection in self.contact_list.contact_selectors(): if cmd and settings.session_traffic_masking: contact = self.contact_list.get_contact(selection) if contact.rx_account != self.uid: raise FunctionReturn("Error: Can't change window during traffic masking.") self.contact = self.contact_list.get_contact(selection) self.window_contacts = [self.contact] self.name = self.contact.nick self.uid = self.contact.rx_account self.imc_name = self.contact.rx_account self.log_messages = self.contact.log_messages self.type = WIN_TYPE_CONTACT self.type_print = 'contact' else: raise FunctionReturn("Error: No contact/group was found.") if settings.session_traffic_masking and not cmd: queues[WINDOW_SELECT_QUEUE].put((self.window_contacts, self.log_messages)) packet = WINDOW_SELECT_HEADER + self.uid.encode() queue_command(packet, settings, queues[COMMAND_PACKET_QUEUE]) clear_screen()
def change_master_key(user_input: 'UserInput', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', queues: Dict[bytes, 'Queue'], master_key: 'MasterKey') -> None: """Change master key on TxM/RxM.""" try: if settings.session_traffic_masking: raise FunctionReturn( "Error: Command is disabled during traffic masking.") try: device = user_input.plaintext.split()[1].lower() except IndexError: raise FunctionReturn("Error: No target system specified.") if device not in [TX, RX]: raise FunctionReturn("Error: Invalid target system.") if device == RX: queue_command(CHANGE_MASTER_K_HEADER, settings, queues[COMMAND_PACKET_QUEUE]) return None old_master_key = master_key.master_key[:] master_key.new_master_key() new_master_key = master_key.master_key phase("Re-encrypting databases") queues[KEY_MANAGEMENT_QUEUE].put( (KDB_CHANGE_MASTER_KEY_HEADER, master_key)) ensure_dir(DIR_USER_DATA) file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs' if os.path.isfile(file_name): re_encrypt(old_master_key, new_master_key, settings) settings.store_settings() contact_list.store_contacts() group_list.store_groups() phase(DONE) box_print("Master key successfully changed.", head=1) clear_screen(delay=1.5) except KeyboardInterrupt: raise FunctionReturn("Password change aborted.", delay=1, head=3, tail_clear=True)
def reset_screens(window: 'Window', settings: 'Settings', c_queue: 'Queue', gateway: 'Gateway') -> None: """Reset screens on TxM/RxM/NH.""" queue_command(RESET_SCREEN_HEADER + window.uid.encode(), settings, c_queue) if not settings.session_trickle: if window.imc_name is not None: im_window = window.imc_name.encode() time.sleep(0.5) transmit(UNENCRYPTED_PACKET_HEADER + UNENCRYPTED_SCREEN_RESET + im_window, settings, gateway) os.system('reset')
def rxm_load_psk(window: 'Window', contact_list: 'ContactList', settings: 'Settings', c_queue: 'Queue') -> None: """Load PSK for selected contact on RxM.""" if settings.session_trickle: raise FunctionReturn("Command disabled during trickle connection.") if window.type == 'group': raise FunctionReturn("Group is selected.") if contact_list.get_contact(window.uid).tx_fingerprint != bytes(32): raise FunctionReturn("Current key was exchanged with X25519.") packet = KEY_EX_PSK_RX_HEADER + window.uid.encode() queue_command(packet, settings, c_queue)
def exit_tfc(settings: 'Settings', c_queue: 'Queue', gateway: 'Gateway') -> None: """Exit TFC on TxM/RxM/NH.""" queue_command(EXIT_PROGRAM_HEADER, settings, c_queue) time.sleep(0.5) transmit(UNENCRYPTED_PACKET_HEADER + UNENCRYPTED_EXIT_COMMAND, settings, gateway) if settings.local_testing_mode: time.sleep(0.8) if settings.data_diode_sockets: time.sleep(2.2) graceful_exit()
def rxm_show_sys_win(user_input: 'UserInput', window: 'TxWindow', settings: 'Settings', c_queue: 'Queue') -> None: """Display system window on RxM until user presses Enter.""" cmd = user_input.plaintext.split()[0] win_name = dict(cmd=LOCAL_ID, fw=WIN_TYPE_FILE)[cmd] command = WINDOW_SELECT_HEADER + win_name.encode() queue_command(command, 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) command = WINDOW_SELECT_HEADER + window.uid.encode() queue_command(command, settings, c_queue)
def group_create(group_name: str, purp_members: List[str], group_list: 'GroupList', contact_list: 'ContactList', settings: 'Settings', queues: Dict[bytes, 'Queue'], _: 'MasterKey') -> None: """Create a new group. Validate group name and determine what members that can be added. """ validate_group_name(group_name, contact_list, group_list) accounts = set(contact_list.get_list_of_accounts()) purp_accounts = set(purp_members) accepted = list(accounts & purp_accounts) rejected = list(purp_accounts - accounts) if len(accepted) > settings.max_number_of_group_members: raise FunctionReturn(f"Error: TFC settings only allow {settings.max_number_of_group_members} members per group.") if len(group_list) == settings.max_number_of_groups: raise FunctionReturn(f"Error: TFC settings only allow {settings.max_number_of_groups} groups.") group_list.add_group(group_name, settings.log_messages_by_default, settings.show_notifications_by_default, members=[contact_list.get_contact(c) for c in accepted]) fields = [f.encode() for f in ([group_name] + accepted)] command = GROUP_CREATE_HEADER + US_BYTE.join(fields) queue_command(command, settings, queues[COMMAND_PACKET_QUEUE]) group_management_print(NEW_GROUP, accepted, contact_list, group_name) group_management_print(UNKNOWN_ACCOUNTS, rejected, contact_list, group_name) if accepted: if yes("Publish list of group members to participants?"): for member in accepted: m_list = [m for m in accepted if m != member] queue_message(user_input=UserInput(US_STR.join([group_name] + m_list), MESSAGE), window =MockWindow(member, [contact_list.get_contact(member)]), settings =settings, m_queue =queues[MESSAGE_PACKET_QUEUE], header =GROUP_MSG_INVITEJOIN_HEADER, log_as_ph =True) else: box_print(f"Created an empty group '{group_name}'", head=1)
def rxm_load_psk(window: 'TxWindow', contact_list: 'ContactList', settings: 'Settings', c_queue: 'Queue') -> None: """Load PSK for selected contact on RxM.""" if settings.session_traffic_masking: raise FunctionReturn("Error: Command is disabled during traffic masking.") if window.type == WIN_TYPE_GROUP: raise FunctionReturn("Error: Group is selected.") if contact_list.get_contact(window.uid).tx_fingerprint != bytes(FINGERPRINT_LEN): raise FunctionReturn("Error: Current key was exchanged with X25519.") packet = KEY_EX_PSK_RX_HEADER + window.uid.encode() queue_command(packet, settings, c_queue)
def test_queue_command(self): self.assertIsNone( queue_command(os.urandom(200), self.settings, self.c_queue)) time.sleep(0.1) c_pt, settings_ = self.c_queue.get() self.assertEqual(len(c_pt), ASSEMBLY_PACKET_LEN) self.assertIsInstance(settings_, Settings)
def group_rm_member(group_name: str, purp_members: List[str], group_list: 'GroupList', contact_list: 'ContactList', settings: 'Settings', queues: Dict[bytes, 'Queue'], master_key: 'MasterKey') -> None: """Remove member(s) from group or group itself.""" if not purp_members: group_rm_group(group_name, group_list, settings, queues, master_key) if group_name not in group_list.get_list_of_group_names(): raise FunctionReturn(f"Group '{group_name}' does not exist.") purp_accounts = set(purp_members) accounts = set(contact_list.get_list_of_accounts()) before_removal = set(group_list.get_group(group_name).get_list_of_member_accounts()) ok_accounts_set = set(purp_accounts & accounts) removable_set = set(before_removal & ok_accounts_set) end_assembly = list(before_removal - removable_set) not_in_group = list(ok_accounts_set - before_removal) rejected = list(purp_accounts - accounts) removable = list(removable_set) ok_accounts = list(ok_accounts_set) group = group_list.get_group(group_name) group.remove_members(removable) fields = [f.encode() for f in ([group_name] + ok_accounts)] command = GROUP_REMOVE_M_HEADER + US_BYTE.join(fields) queue_command(command, settings, queues[COMMAND_PACKET_QUEUE]) group_management_print(REMOVED_MEMBERS, removable, contact_list, group_name) group_management_print(NOT_IN_GROUP, not_in_group, contact_list, group_name) group_management_print(UNKNOWN_ACCOUNTS, rejected, contact_list, group_name) if removable and end_assembly and yes("Publish list of removed members to remaining members?"): for member in end_assembly: queue_message(user_input=UserInput(US_STR.join([group_name] + removable), MESSAGE), window =MockWindow(member, [contact_list.get_contact(member)]), settings =settings, m_queue =queues[MESSAGE_PACKET_QUEUE], header =GROUP_MSG_MEMBER_REM_HEADER, log_as_ph =True)
def remove_contact(user_input: 'UserInput', window: 'Window', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None: """Remove contact on TxM/RxM.""" if settings.session_trickle: raise FunctionReturn("Command disabled during trickle connection.") try: selection = user_input.plaintext.split()[1] except IndexError: raise FunctionReturn("Error: No account specified.") if not yes(f"Remove {selection} completely?", head=1): raise FunctionReturn("Removal of contact aborted.") # Load account if user enters nick if selection in contact_list.get_list_of_nicks(): selection = contact_list.get_contact(selection).rx_account packet = CONTACT_REMOVE_HEADER + selection.encode() queue_command(packet, settings, queues[COMMAND_PACKET_QUEUE]) if selection in contact_list.get_list_of_accounts(): queues[KEY_MANAGEMENT_QUEUE].put(('REM', selection)) contact_list.remove_contact(selection) box_print(f"Removed {selection} from contacts.", head=1, tail=1) else: box_print(f"TxM has no {selection} to remove.", head=1, tail=1) if any([g.remove_members([selection]) for g in group_list]): box_print(f"Removed {selection} from group(s).", tail=1) for c in window: if selection == c.rx_account: if window.type == 'contact': window.deselect() elif window.type == 'group': window.update_group_win_members(group_list) # If last member from group is removed, deselect group. # This is not done in update_group_win_members because # It would prevent selecting the empty group for group # related commands such as notifications. if not window.window_contacts: window.deselect()
def change_master_key(user_input: 'UserInput', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', queues: Dict[bytes, 'Queue'], master_key: 'MasterKey') -> None: """Change master key on TxM/RxM.""" try: if settings.session_trickle: raise FunctionReturn("Command disabled during trickle connection.") try: device = user_input.plaintext.split()[1] except IndexError: raise FunctionReturn("No target system specified.") if device.lower() not in ['tx', 'txm', 'rx', 'rxm']: raise FunctionReturn("Invalid target system.") if device.lower() in ['rx', 'rxm']: queue_command(CHANGE_MASTER_K_HEADER, settings, queues[COMMAND_PACKET_QUEUE]) print('') return None old_master_key = master_key.master_key[:] master_key.new_master_key() new_master_key = master_key.master_key ensure_dir(f'{DIR_USER_DATA}/') file_name = f'{DIR_USER_DATA}/{settings.software_operation}_logs' if os.path.isfile(file_name): phase("Re-encrypting log-file") re_encrypt(old_master_key, new_master_key, settings) phase("Done") queues[KEY_MANAGEMENT_QUEUE].put(('KEY', master_key)) settings.store_settings() contact_list.store_contacts() group_list.store_groups() box_print("Master key successfully changed.", head=1) clear_screen(delay=1.5) except KeyboardInterrupt: raise FunctionReturn("Password change aborted.")
def exit_tfc(settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None: """Exit TFC on TxM/RxM/NH.""" for q in [COMMAND_PACKET_QUEUE, NH_PACKET_QUEUE]: while queues[q].qsize() != 0: queues[q].get() queue_command(EXIT_PROGRAM_HEADER, settings, queues[COMMAND_PACKET_QUEUE]) if not settings.session_traffic_masking: if settings.local_testing_mode: time.sleep(0.8) if settings.data_diode_sockets: time.sleep(2.2) else: time.sleep(settings.race_condition_delay) queue_to_nh(UNENCRYPTED_PACKET_HEADER + UNENCRYPTED_EXIT_COMMAND, settings, queues[NH_PACKET_QUEUE])
def print_logs(user_input: 'UserInput', window: 'Window', contact_list: 'ContactList', settings: 'Settings', c_queue: 'Queue', master_key: 'MasterKey') -> None: """Print log files on screen.""" try: no_messages_str = user_input.plaintext.split()[1] if not no_messages_str.isdigit(): raise FunctionReturn("Specified invalid number of messages to print.") no_messages = int(no_messages_str) except IndexError: no_messages = 0 packet = LOG_DISPLAY_HEADER + window.uid.encode() + US_BYTE + int_to_bytes(no_messages) queue_command(packet, settings, c_queue) access_history(window, contact_list, settings, master_key, no_messages)
def change_setting(user_input: 'UserInput', contact_list: 'ContactList', group_list: 'GroupList', settings: 'Settings', c_queue: 'Queue', gateway: 'Gateway') -> None: """Change setting on TxM / RxM.""" try: key = user_input.plaintext.split()[1] except IndexError: raise FunctionReturn("No setting specified.") try: _ = user_input.plaintext.split()[2] except IndexError: raise FunctionReturn("No value for setting specified.") value = ' '.join(user_input.plaintext.split()[2:]) if key not in settings.key_list: raise FunctionReturn(f"Invalid setting {key}.") if settings.session_trickle: if key in ['e_correction_ratio', 'serial_iface_speed']: raise FunctionReturn("Change of setting disabled during trickle connection.") settings.change_setting(key, value, contact_list, group_list) if key == 'e_correction_ratio': time.sleep(0.5) transmit(UNENCRYPTED_PACKET_HEADER + UNENCRYPTED_EC_RATIO + value.encode(), settings, gateway) if key == 'serial_iface_speed': time.sleep(0.5) transmit(UNENCRYPTED_PACKET_HEADER + UNENCRYPTED_BAUDRATE + value.encode(), settings, gateway) if key == 'disable_gui_dialog': time.sleep(0.5) transmit(UNENCRYPTED_PACKET_HEADER + UNENCRYPTED_GUI_DIALOG + value.encode(), settings, gateway) packet = CHANGE_SETTING_HEADER + key.encode() + US_BYTE + value.encode() queue_command(packet, settings, c_queue)