Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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}'")
Ejemplo n.º 3
0
    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()
Ejemplo n.º 4
0
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')
Ejemplo n.º 5
0
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}.")
Ejemplo n.º 6
0
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])
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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')
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
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()
Ejemplo n.º 11
0
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.")
Ejemplo n.º 12
0
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)
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
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)
Ejemplo n.º 15
0
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)
Ejemplo n.º 16
0
    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()
Ejemplo n.º 17
0
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)
Ejemplo n.º 18
0
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')
Ejemplo n.º 19
0
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)
Ejemplo n.º 20
0
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()
Ejemplo n.º 21
0
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)
Ejemplo n.º 22
0
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)
Ejemplo n.º 23
0
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)
Ejemplo n.º 24
0
    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)
Ejemplo n.º 25
0
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)
Ejemplo n.º 26
0
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()
Ejemplo n.º 27
0
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.")
Ejemplo n.º 28
0
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])
Ejemplo n.º 29
0
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)
Ejemplo n.º 30
0
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)