Esempio n. 1
0
    def test_onion_service(self, *_):
        # Setup
        queues = gen_queue_dict()

        def queue_delayer():
            """Place Onion Service data into queue after delay."""
            time.sleep(0.5)
            queues[ONION_KEY_QUEUE].put((bytes(ONION_SERVICE_PRIVATE_KEY_LENGTH), b'\x01'))
            queues[ONION_KEY_QUEUE].put((bytes(ONION_SERVICE_PRIVATE_KEY_LENGTH), b'\x01'))
            time.sleep(0.1)
            queues[ONION_CLOSE_QUEUE].put(EXIT)

        threading.Thread(target=queue_delayer).start()

        # Test
        with mock.patch("time.sleep", return_value=None):
            self.assertIsNone(onion_service(queues))

        port, address = queues[TOR_DATA_QUEUE].get()
        self.assertIsInstance(port, int)
        self.assertEqual(validate_onion_addr(address), '')
        self.assertEqual(queues[EXIT_QUEUE].get(), EXIT)

        # Teardown
        tear_queues(queues)
Esempio n. 2
0
def determine_selector(selection: str, contact_list: 'ContactList',
                       group_list: 'GroupList') -> bytes:
    """Determine selector (group ID or Onion Service public key)."""
    if selection in contact_list.contact_selectors():
        selector = contact_list.get_contact_by_address_or_nick(
            selection).onion_pub_key

    elif selection in group_list.get_list_of_group_names():
        selector = group_list.get_group(selection).group_id

    elif len(selection) == ONION_ADDRESS_LENGTH:
        if validate_onion_addr(selection):
            raise SoftError("Error: Invalid account.", head_clear=True)
        selector = onion_address_to_pub_key(selection)

    elif len(selection) == GROUP_ID_ENC_LENGTH:
        try:
            selector = b58decode(selection)
        except ValueError:
            raise SoftError("Error: Invalid group ID.", head_clear=True)

    else:
        raise SoftError("Error: Unknown selector.", head_clear=True)

    return selector
Esempio n. 3
0
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
Esempio n. 4
0
 def test_validate_account(self):
     user_account = nick_to_onion_address("Bob")
     self.assertEqual(
         validate_onion_addr(nick_to_onion_address("Alice"), user_account),
         '')
     self.assertEqual(
         validate_onion_addr(nick_to_onion_address("Bob"), user_account),
         'Error: Can not add own account.')
     self.assertEqual(
         validate_onion_addr(
             nick_to_onion_address("Alice")[:-1] + 'a', user_account),
         'Checksum error - Check that the entered account is correct.')
     self.assertEqual(
         validate_onion_addr(
             nick_to_onion_address("Alice")[:-1] + '%', user_account),
         'Error: Invalid account format.')
     self.assertEqual(
         validate_onion_addr(
             nick_to_onion_address("Alice") + 'a', user_account),
         'Error: Invalid account format.')
     self.assertEqual(
         validate_onion_addr(
             nick_to_onion_address("Alice")[:-1] + '€', user_account),
         'Error: Invalid account format.')
     self.assertEqual(validate_onion_addr(LOCAL_ID, user_account),
                      'Error: Can not add reserved account.')
Esempio n. 5
0
 def test_validate_account(self) -> None:
     user_account = nick_to_onion_address("Bob")
     self.assertEqual(
         validate_onion_addr(
             nick_to_onion_address("Alice") + 'a', user_account),
         'Error: Invalid account length.')
     self.assertEqual(
         validate_onion_addr(
             nick_to_onion_address("Alice").upper(), user_account),
         'Error: Account must be in lower case.')
     self.assertEqual(
         validate_onion_addr(
             nick_to_onion_address("Alice")[:-1] + 'a', user_account),
         'Checksum error - Check that the entered account is correct.')
     self.assertEqual(
         validate_onion_addr(
             nick_to_onion_address("Alice")[:-1] + '%', user_account),
         'Error: Invalid account format.')
     self.assertEqual(validate_onion_addr(LOCAL_ID, user_account),
                      'Error: Can not add reserved account.')
     self.assertEqual(
         validate_onion_addr(nick_to_onion_address("Bob"), user_account),
         'Error: Can not add own account.')
     self.assertEqual(
         validate_onion_addr(nick_to_onion_address("Alice"), user_account),
         '')
Esempio n. 6
0
    def evaluate_account(self) -> None:
        """Check if the input is a valid TFC account."""
        purp_acco = self.address_entry_box.get()  # type: ignore
        error_msg = validate_onion_addr(purp_acco, self.onion_address_user)

        if error_msg:
            self.address_entry_box.delete(0, tkinter.END)
            self.error_label.forget()
            self.error_label.configure(text=error_msg, justify='center')
            self.error_label.grid(row=3, columnspan=2, sticky='NSEW')
        else:
            self.queue.put(purp_acco)
            self.root.destroy()
Esempio n. 7
0
def c_req_manager(queues: 'QueueDict', unit_test: bool = False) -> None:
    """Manage incoming contact requests."""
    existing_contacts = []  # type: List[bytes]
    contact_requests = []  # type: List[bytes]

    request_queue = queues[CONTACT_REQ_QUEUE]
    contact_queue = queues[C_REQ_MGMT_QUEUE]
    setting_queue = queues[C_REQ_STATE_QUEUE]
    show_requests = True

    while True:
        with ignored(EOFError, KeyboardInterrupt):
            while request_queue.qsize() == 0:
                time.sleep(0.1)
            purp_onion_address = request_queue.get()

            while setting_queue.qsize() != 0:
                show_requests = setting_queue.get()

            # Update list of existing contacts
            while contact_queue.qsize() > 0:
                command, ser_onion_pub_keys = contact_queue.get()
                onion_pub_key_list = split_byte_string(
                    ser_onion_pub_keys, ONION_SERVICE_PUBLIC_KEY_LENGTH)

                if command == RP_ADD_CONTACT_HEADER:
                    existing_contacts = list(
                        set(existing_contacts) | set(onion_pub_key_list))
                elif command == RP_REMOVE_CONTACT_HEADER:
                    existing_contacts = list(
                        set(existing_contacts) - set(onion_pub_key_list))

            if validate_onion_addr(purp_onion_address) == '':
                onion_pub_key = onion_address_to_pub_key(purp_onion_address)
                if onion_pub_key in existing_contacts:
                    continue
                if onion_pub_key in contact_requests:
                    continue

                if show_requests:
                    ts_fmt = datetime.now().strftime(
                        '%b %d - %H:%M:%S.%f')[:-4]
                    m_print([
                        f"{ts_fmt} - New contact request from an unknown TFC account:",
                        purp_onion_address
                    ],
                            box=True)
                contact_requests.append(onion_pub_key)

            if unit_test and queues[UNIT_TEST_QUEUE].qsize() != 0:
                break
Esempio n. 8
0
def remove_contact(user_input: 'UserInput', window: 'TxWindow',
                   contact_list: 'ContactList', group_list: 'GroupList',
                   settings: 'Settings', queues: 'QueueDict',
                   master_key: 'MasterKey') -> None:
    """Remove contact from TFC."""
    if settings.traffic_masking:
        raise SoftError("Error: Command is disabled during traffic masking.",
                        head_clear=True)

    try:
        selection = user_input.plaintext.split()[1]
    except IndexError:
        raise SoftError("Error: No account specified.", head_clear=True)

    if not yes(f"Remove contact '{selection}'?", abort=False, head=1):
        raise SoftError("Removal of contact aborted.",
                        head=0,
                        delay=1,
                        tail_clear=True)

    if selection in contact_list.contact_selectors():
        onion_pub_key = contact_list.get_contact_by_address_or_nick(
            selection).onion_pub_key

    else:
        if validate_onion_addr(selection):
            raise SoftError("Error: Invalid selection.",
                            head=0,
                            delay=1,
                            tail_clear=True)
        onion_pub_key = onion_address_to_pub_key(selection)

    receiver_command = CONTACT_REM + onion_pub_key
    queue_command(receiver_command, settings, queues)

    with ignored(SoftError):
        remove_logs(contact_list, group_list, settings, master_key,
                    onion_pub_key)

    queues[KEY_MANAGEMENT_QUEUE].put((KDB_REMOVE_ENTRY_HEADER, onion_pub_key))

    relay_command = UNENCRYPTED_DATAGRAM_HEADER + UNENCRYPTED_REM_CONTACT + onion_pub_key
    queue_to_nc(relay_command, queues[RELAY_PACKET_QUEUE])

    target = determine_target(selection, onion_pub_key, contact_list)

    if any([g.remove_members([onion_pub_key]) for g in group_list]):
        m_print(f"Removed {target} from group(s).", tail=1)

    check_for_window_deselection(onion_pub_key, window, group_list)
Esempio n. 9
0
    def test_reserved_accounts_are_valid(self):
        """\
        Each used account placeholder should be a valid, but reserved
        account.
        """
        reserved_accounts = [src.common.statics.LOCAL_ID,
                             src.common.statics.DUMMY_CONTACT,
                             src.common.statics.DUMMY_MEMBER]

        for account in reserved_accounts:
            self.assertEqual(validate_onion_addr(account), "Error: Can not add reserved account.")

        # Test each account is unique.
        self.assertEqual(len(reserved_accounts),
                         len(set(reserved_accounts)))
Esempio n. 10
0
def c_req_manager(queues: 'QueueDict', unit_test: bool = False) -> None:
    """Manage displayed contact requests."""
    existing_contacts = []  # type: List[bytes]
    displayed_requests = []  # type: List[bytes]

    request_queue = queues[CONTACT_REQ_QUEUE]
    contact_queue = queues[C_REQ_MGMT_QUEUE]
    setting_queue = queues[C_REQ_STATE_QUEUE]
    account_queue = queues[ACCOUNT_SEND_QUEUE]
    show_requests = True

    while True:
        with ignored(EOFError, KeyboardInterrupt):
            while request_queue.qsize() == 0:
                time.sleep(0.1)
            purp_onion_address = request_queue.get()

            while setting_queue.qsize() != 0:
                show_requests = setting_queue.get()

            existing_contacts = update_list_of_existing_contacts(
                contact_queue, existing_contacts)

            if validate_onion_addr(purp_onion_address) == '':
                onion_pub_key = onion_address_to_pub_key(purp_onion_address)
                if onion_pub_key in existing_contacts:
                    continue
                if onion_pub_key in displayed_requests:
                    continue

                if show_requests:
                    ts = datetime.now().strftime('%b %d - %H:%M:%S.%f')[:-4]
                    m_print([
                        f"{ts} - New contact request from an unknown TFC account:",
                        purp_onion_address
                    ],
                            box=True)
                    account_queue.put(purp_onion_address)

                displayed_requests.append(onion_pub_key)

            if unit_test and queues[UNIT_TEST_QUEUE].qsize() != 0:
                break
Esempio n. 11
0
def remove_log(user_input: 'UserInput', contact_list: 'ContactList',
               group_list: 'GroupList', settings: 'Settings',
               queues: 'QueueDict', master_key: 'MasterKey') -> None:
    """Remove log entries for contact or group."""
    try:
        selection = user_input.plaintext.split()[1]
    except IndexError:
        raise FunctionReturn("Error: No contact/group specified.",
                             head_clear=True)

    if not yes(f"Remove logs for {selection}?", abort=False, head=1):
        raise FunctionReturn("Log file removal aborted.",
                             tail_clear=True,
                             delay=1,
                             head=0)

    # Determine selector (group ID or Onion Service public key) from command parameters
    if selection in contact_list.contact_selectors():
        selector = contact_list.get_contact_by_address_or_nick(
            selection).onion_pub_key

    elif selection in group_list.get_list_of_group_names():
        selector = group_list.get_group(selection).group_id

    elif len(selection) == ONION_ADDRESS_LENGTH:
        if validate_onion_addr(selection):
            raise FunctionReturn("Error: Invalid account.", head_clear=True)
        selector = onion_address_to_pub_key(selection)

    elif len(selection) == GROUP_ID_ENC_LENGTH:
        try:
            selector = b58decode(selection)
        except ValueError:
            raise FunctionReturn("Error: Invalid group ID.", head_clear=True)

    else:
        raise FunctionReturn("Error: Unknown selector.", head_clear=True)

    # Remove logs that match the selector
    command = LOG_REMOVE + selector
    queue_command(command, settings, queues)

    remove_logs(contact_list, group_list, settings, master_key, selector)
Esempio n. 12
0
    def test_onion_service_key_generation_and_load(self, _: Any) -> None:
        onion_service = OnionService(self.master_key)

        # Test new OnionService has valid attributes
        self.assertIsInstance(onion_service.master_key, MasterKey)
        self.assertIsInstance(onion_service.onion_private_key, bytes)
        self.assertIsInstance(onion_service.user_onion_address, str)
        self.assertFalse(onion_service.is_delivered)
        self.assertEqual(validate_onion_addr(onion_service.user_onion_address),
                         '')

        # Test data is stored to a database
        self.assertTrue(os.path.isfile(self.file_name))
        self.assertEqual(
            os.path.getsize(self.file_name), XCHACHA20_NONCE_LENGTH +
            ONION_SERVICE_PRIVATE_KEY_LENGTH + POLY1305_TAG_LENGTH)

        # Test data can be loaded from the database
        onion_service2 = OnionService(self.master_key)
        self.assertIsInstance(onion_service2.onion_private_key, bytes)
        self.assertEqual(onion_service.onion_private_key,
                         onion_service2.onion_private_key)
Esempio n. 13
0
def remove_contact(user_input: 'UserInput', window: 'TxWindow',
                   contact_list: 'ContactList', group_list: 'GroupList',
                   settings: 'Settings', queues: 'QueueDict',
                   master_key: 'MasterKey') -> None:
    """Remove contact from TFC."""
    if settings.traffic_masking:
        raise FunctionReturn(
            "Error: Command is disabled during traffic masking.",
            head_clear=True)

    try:
        selection = user_input.plaintext.split()[1]
    except IndexError:
        raise FunctionReturn("Error: No account specified.", head_clear=True)

    if not yes(f"Remove contact '{selection}'?", abort=False, head=1):
        raise FunctionReturn("Removal of contact aborted.",
                             head=0,
                             delay=1,
                             tail_clear=True)

    if selection in contact_list.contact_selectors():
        onion_pub_key = contact_list.get_contact_by_address_or_nick(
            selection).onion_pub_key

    else:
        if validate_onion_addr(selection):
            raise FunctionReturn("Error: Invalid selection.",
                                 head=0,
                                 delay=1,
                                 tail_clear=True)
        else:
            onion_pub_key = onion_address_to_pub_key(selection)

    receiver_command = CONTACT_REM + onion_pub_key
    queue_command(receiver_command, settings, queues)

    with ignored(FunctionReturn):
        remove_logs(contact_list, group_list, settings, master_key,
                    onion_pub_key)

    queues[KEY_MANAGEMENT_QUEUE].put((KDB_REMOVE_ENTRY_HEADER, onion_pub_key))

    relay_command = UNENCRYPTED_DATAGRAM_HEADER + UNENCRYPTED_REM_CONTACT + onion_pub_key
    queue_to_nc(relay_command, queues[RELAY_PACKET_QUEUE])

    if onion_pub_key in contact_list.get_list_of_pub_keys():
        contact = contact_list.get_contact_by_pub_key(onion_pub_key)
        target = f"{contact.nick} ({contact.short_address})"
        contact_list.remove_contact_by_pub_key(onion_pub_key)
        m_print(f"Removed {target} from contacts.", head=1, tail=1)
    else:
        target = f"{selection[:TRUNC_ADDRESS_LENGTH]}"
        m_print(f"Transmitter has no {target} to remove.", head=1, tail=1)

    if any([g.remove_members([onion_pub_key]) for g in group_list]):
        m_print(f"Removed {target} from group(s).", tail=1)

    if window.type == WIN_TYPE_CONTACT:
        if onion_pub_key == window.uid:
            window.deselect()

    if window.type == WIN_TYPE_GROUP:
        for c in window:
            if c.onion_pub_key == onion_pub_key:
                window.update_window(group_list)

                # If the last member of the group is removed, deselect
                # the 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()