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)
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
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 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.')
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), '')
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()
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
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)
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)))
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
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)
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)
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()