class TestLocalKey(TFCTestCase): def setUp(self): self.contact_list = ContactList() self.settings = Settings() self.queues = gen_queue_dict() self.args = self.contact_list, self.settings, self.queues def tearDown(self): tear_queues(self.queues) def test_new_local_key_when_traffic_masking_is_enabled_raises_fr(self): self.settings.traffic_masking = True self.contact_list.contacts = [create_contact(LOCAL_ID)] self.assert_fr("Error: Command is disabled during traffic masking.", new_local_key, *self.args) @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=['bad', '', '61']) @mock.patch('os.getrandom', side_effect=[ SYMMETRIC_KEY_LENGTH * b'a', SYMMETRIC_KEY_LENGTH * b'a', SYMMETRIC_KEY_LENGTH * b'a', XCHACHA20_NONCE_LENGTH * b'a', SYMMETRIC_KEY_LENGTH * b'a', SYMMETRIC_KEY_LENGTH * b'a' ]) @mock.patch('os.urandom', return_value=CONFIRM_CODE_LENGTH * b'a') def test_new_local_key(self, *_): # Setup self.settings.nc_bypass_messages = False self.settings.traffic_masking = False # Test self.assertIsNone(new_local_key(*self.args)) local_contact = self.contact_list.get_contact_by_pub_key(LOCAL_PUBKEY) self.assertEqual(local_contact.onion_pub_key, LOCAL_PUBKEY) self.assertEqual(local_contact.nick, LOCAL_NICK) self.assertEqual(local_contact.tx_fingerprint, bytes(FINGERPRINT_LENGTH)) self.assertEqual(local_contact.rx_fingerprint, bytes(FINGERPRINT_LENGTH)) self.assertFalse(local_contact.log_messages) self.assertFalse(local_contact.file_reception) self.assertFalse(local_contact.notifications) self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 1) cmd, account, tx_key, rx_key, tx_hek, rx_hek = self.queues[ KEY_MANAGEMENT_QUEUE].get() self.assertEqual(cmd, KDB_ADD_ENTRY_HEADER) self.assertEqual(account, LOCAL_PUBKEY) for key in [tx_key, rx_key, tx_hek, rx_hek]: self.assertIsInstance(key, bytes) self.assertEqual(len(key), SYMMETRIC_KEY_LENGTH) @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=KeyboardInterrupt) @mock.patch('os.getrandom', lambda x, flags: x * b'a') def test_keyboard_interrupt_raises_fr(self, *_): self.assert_fr("Local key setup aborted.", new_local_key, *self.args)
class TestChNick(TFCTestCase): def setUp(self): self.ts = datetime.now() self.contact_list = ContactList(nicks=['Alice']) self.window_list = WindowList(contact_list=self.contact_list) self.group_list = GroupList() self.args = self.ts, self.window_list, self.contact_list self.window = self.window_list.get_window(nick_to_pub_key("Alice")) self.window.type = WIN_TYPE_CONTACT def test_unknown_account_raises_fr(self): # Setup cmd_data = nick_to_pub_key("Bob") + b'Bob_' # Test trunc_addr = nick_to_short_address('Bob') self.assert_fr(f"Error: Receiver has no contact '{trunc_addr}' to rename.", ch_nick, cmd_data, *self.args) def test_nick_change(self): # Setup cmd_data = nick_to_pub_key("Alice") + b'Alice_' # Test self.assertIsNone(ch_nick(cmd_data, *self.args)) self.assertEqual(self.contact_list.get_contact_by_pub_key(nick_to_pub_key("Alice")).nick, 'Alice_') self.assertEqual(self.window.name, 'Alice_')
class TestChangeNick(TFCTestCase): def setUp(self) -> None: """Pre-test actions.""" self.contact_list = ContactList(nicks=['Alice']) self.group_list = GroupList() self.settings = Settings() self.queues = gen_queue_dict() self.args = self.contact_list, self.group_list, self.settings, self.queues def tearDown(self) -> None: """Post-test actions.""" tear_queues(self.queues) def test_missing_nick_raises_soft_error(self) -> None: self.assert_se("Error: No nick specified.", change_nick, UserInput("nick "), TxWindow(type=WIN_TYPE_CONTACT), *self.args) def test_invalid_nick_raises_soft_error(self) -> None: # Setup window = TxWindow(type=WIN_TYPE_CONTACT, contact=create_contact('Bob')) # Test self.assert_se("Error: Nick must be printable.", change_nick, UserInput("nick Alice\x01"), window, *self.args) def test_no_contact_raises_soft_error(self) -> None: # Setup window = TxWindow(type=WIN_TYPE_CONTACT, contact=create_contact('Bob')) window.contact = None # Test self.assert_se("Error: Window does not have contact.", change_nick, UserInput("nick Alice\x01"), window, *self.args) def test_successful_nick_change(self) -> None: # Setup window = TxWindow(name='Alice', type=WIN_TYPE_CONTACT, contact=self.contact_list.get_contact_by_address_or_nick('Alice')) # Test self.assertIsNone(change_nick(UserInput("nick Alice_"), window, *self.args)) self.assertEqual(self.contact_list.get_contact_by_pub_key(nick_to_pub_key('Alice')).nick, 'Alice_') @mock.patch('time.sleep', return_value=None) def test_successful_group_nick_change(self, _: Any) -> None: # Setup group = create_group('test_group') user_input = UserInput("nick group2") window = TxWindow(name ='test_group', type =WIN_TYPE_GROUP, group=group, uid =group.group_id) # Test self.assert_se("Renamed group 'test_group' to 'group2'.", change_nick, user_input, window, *self.args) self.assertEqual(window.group.name, 'group2')
class TestAddNewContact(TFCTestCase): def setUp(self) -> None: """Pre-test actions.""" self.contact_list = ContactList(nicks=[LOCAL_ID]) self.group_list = GroupList() self.settings = Settings(disable_gui_dialog=True) self.queues = gen_queue_dict() self.onion_service = OnionService() self.args = self.contact_list, self.group_list, self.settings, self.queues, self.onion_service def tearDown(self) -> None: """Post-test actions.""" with ignored(OSError): os.remove(f'v4dkh.psk - Give to hpcra') tear_queues(self.queues) def test_adding_new_contact_during_traffic_masking_raises_soft_error(self) -> None: # Setup self.settings.traffic_masking = True # Test self.assert_se("Error: Command is disabled during traffic masking.", add_new_contact, *self.args) def test_contact_list_full_raises_soft_error(self) -> None: # Setup contact_list = ContactList(nicks=[str(n) for n in range(50)]) self.contact_list.contacts = contact_list.contacts # Test self.assert_se("Error: TFC settings only allow 50 accounts.", add_new_contact, *self.args) @mock.patch('builtins.input', side_effect=[nick_to_onion_address("Bob"), 'Bob', '', VALID_ECDHE_PUB_KEY, 'Yes', blake2b(nick_to_pub_key('Bob'), digest_size=CONFIRM_CODE_LENGTH).hex()]) @mock.patch('shutil.get_terminal_size', return_value=[200, 200]) @mock.patch('time.sleep', return_value=None) def test_default_nick_ecdhe(self, *_: Any) -> None: self.assertIsNone(add_new_contact(*self.args)) contact = self.contact_list.get_contact_by_address_or_nick("Bob") self.assertEqual(contact.nick, 'Bob') self.assertNotEqual(contact.tx_fingerprint, bytes(FINGERPRINT_LENGTH)) @mock.patch('src.transmitter.key_exchanges.ARGON2_PSK_MEMORY_COST', 200) @mock.patch('src.common.statics.MIN_KEY_DERIVATION_TIME', 0.1) @mock.patch('src.common.statics.MAX_KEY_DERIVATION_TIME', 1.0) @mock.patch('builtins.input', side_effect=[nick_to_onion_address("Alice"), 'Alice_', 'psk', '.', '', 'ff', 'fc']) @mock.patch('getpass.getpass', return_value='test_password') @mock.patch('time.sleep', return_value=None) def test_standard_nick_psk_kex(self, *_: Any) -> None: self.onion_service.account = nick_to_onion_address('Bob').encode() self.assertIsNone(add_new_contact(*self.args)) contact = self.contact_list.get_contact_by_pub_key(nick_to_pub_key("Alice")) self.assertEqual(contact.nick, 'Alice_') self.assertEqual(contact.tx_fingerprint, bytes(FINGERPRINT_LENGTH)) @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=KeyboardInterrupt) def test_keyboard_interrupt_raises_soft_error(self, *_: Any) -> None: self.assert_se('Contact creation aborted.', add_new_contact, *self.args)
class TestPSK(TFCTestCase): def setUp(self) -> None: """Pre-test actions.""" self.unit_test_dir = cd_unit_test() self.contact_list = ContactList() self.settings = Settings(disable_gui_dialog=True) self.queues = gen_queue_dict() self.onion_service = OnionService() self.args = self.contact_list, self.settings, self.onion_service, self.queues def tearDown(self) -> None: """Post-test actions.""" cleanup(self.unit_test_dir) with ignored(OSError): os.remove(f"{self.onion_service.user_short_address}.psk - Give to {nick_to_short_address('Alice')}") tear_queues(self.queues) @mock.patch('builtins.input', side_effect=['/root/', '.', 'fc']) @mock.patch('time.sleep', return_value=None) @mock.patch('getpass.getpass', return_value='test_password') @mock.patch('src.transmitter.key_exchanges.ARGON2_PSK_MEMORY_COST', 1000) @mock.patch('src.transmitter.key_exchanges.ARGON2_PSK_TIME_COST', 1) def test_psk_creation(self, *_: Any) -> None: self.assertIsNone(create_pre_shared_key(nick_to_pub_key("Alice"), 'Alice', *self.args)) contact = self.contact_list.get_contact_by_pub_key(nick_to_pub_key("Alice")) self.assertEqual(contact.onion_pub_key, nick_to_pub_key("Alice")) self.assertEqual(contact.nick, 'Alice') self.assertEqual(contact.tx_fingerprint, bytes(FINGERPRINT_LENGTH)) self.assertEqual(contact.rx_fingerprint, bytes(FINGERPRINT_LENGTH)) self.assertEqual(contact.kex_status, KEX_STATUS_NO_RX_PSK) self.assertFalse(contact.log_messages) self.assertFalse(contact.file_reception) self.assertTrue(contact.notifications) cmd, account, tx_key, rx_key, tx_hek, rx_hek = self.queues[KEY_MANAGEMENT_QUEUE].get() self.assertEqual(cmd, KDB_ADD_ENTRY_HEADER) self.assertEqual(account, nick_to_pub_key("Alice")) for key in [tx_key, rx_key, tx_hek, rx_hek]: self.assertIsInstance(key, bytes) self.assertEqual(len(key), SYMMETRIC_KEY_LENGTH) self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 1) self.assertTrue(os.path.isfile( f"{self.onion_service.user_short_address}.psk - Give to {nick_to_short_address('Alice')}")) @mock.patch('time.sleep', return_value=None) @mock.patch('getpass.getpass', side_effect=KeyboardInterrupt) def test_keyboard_interrupt_raises_se(self, *_: Any) -> None: self.assert_se("PSK generation aborted.", create_pre_shared_key, nick_to_pub_key("Alice"), 'Alice', *self.args)
class TestKeyExPSKTx(TFCTestCase): def setUp(self): """Pre-test actions.""" self.ts = datetime.fromtimestamp(1502750000) self.window_list = WindowList(nicks=[LOCAL_ID]) self.contact_list = ContactList() self.key_list = KeyList() self.settings = Settings() self.packet = (nick_to_pub_key("Alice") + SYMMETRIC_KEY_LENGTH * b'\x01' + bytes(SYMMETRIC_KEY_LENGTH) + SYMMETRIC_KEY_LENGTH * b'\x02' + bytes(SYMMETRIC_KEY_LENGTH) + str_to_bytes('Alice')) self.args = self.packet, self.ts, self.window_list, self.contact_list, self.key_list, self.settings @mock.patch('time.sleep', return_value=None) def test_invalid_nick_raises_fr(self, _): self.packet = (nick_to_pub_key("Alice") + SYMMETRIC_KEY_LENGTH * b'\x01' + bytes(SYMMETRIC_KEY_LENGTH) + SYMMETRIC_KEY_LENGTH * b'\x02' + bytes(SYMMETRIC_KEY_LENGTH) + UNDECODABLE_UNICODE) self.args = self.packet, self.ts, self.window_list, self.contact_list, self.key_list, self.settings self.assert_fr("Error: Received invalid contact data", key_ex_psk_tx, *self.args) @mock.patch('time.sleep', return_value=None) def test_add_psk_tx_keys(self, _): self.assertIsNone(key_ex_psk_tx(*self.args)) keyset = self.key_list.get_keyset(nick_to_pub_key("Alice")) self.assertIsInstance(keyset, KeySet) self.assertEqual(keyset.onion_pub_key, nick_to_pub_key("Alice")) self.assertEqual(keyset.tx_mk, SYMMETRIC_KEY_LENGTH * b'\x01') self.assertEqual(keyset.rx_mk, bytes(SYMMETRIC_KEY_LENGTH)) self.assertEqual(keyset.tx_hk, SYMMETRIC_KEY_LENGTH * b'\x02') self.assertEqual(keyset.rx_hk, bytes(SYMMETRIC_KEY_LENGTH)) contact = self.contact_list.get_contact_by_pub_key(nick_to_pub_key("Alice")) self.assertIsInstance(contact, Contact) self.assertEqual(contact.onion_pub_key, nick_to_pub_key("Alice")) self.assertEqual(contact.nick, 'Alice') self.assertEqual(contact.tx_fingerprint, bytes(FINGERPRINT_LENGTH)) self.assertEqual(contact.rx_fingerprint, bytes(FINGERPRINT_LENGTH))
class TestKeyExchange(TFCTestCase): def setUp(self): self.contact_list = ContactList() self.settings = Settings() self.queues = gen_queue_dict() self.args = self.contact_list, self.settings, self.queues def tearDown(self): tear_queues(self.queues) @mock.patch('shutil.get_terminal_size', return_value=[200, 200]) @mock.patch('builtins.input', return_value=b58encode(bytes(TFC_PUBLIC_KEY_LENGTH), public_key=True)) def test_zero_public_key_raises_fr(self, *_): self.assert_fr("Error: Zero public key", start_key_exchange, nick_to_pub_key("Alice"), 'Alice', *self.args) @mock.patch('shutil.get_terminal_size', return_value=[200, 200]) @mock.patch('builtins.input', return_value=b58encode((TFC_PUBLIC_KEY_LENGTH - 1) * b'a', public_key=True)) def test_invalid_public_key_length_raises_fr(self, *_): self.assert_fr("Error: Invalid public key length", start_key_exchange, nick_to_pub_key("Alice"), 'Alice', *self.args) @mock.patch( 'builtins.input', side_effect=[ '', # Empty message should resend key VALID_ECDHE_PUB_KEY[:-1], # Short key should fail VALID_ECDHE_PUB_KEY + 'a', # Long key should fail VALID_ECDHE_PUB_KEY[:-1] + 'a', # Invalid key should fail VALID_ECDHE_PUB_KEY, # Correct key 'No' ]) # Fingerprint mismatch) @mock.patch('time.sleep', return_value=None) @mock.patch('shutil.get_terminal_size', return_value=[200, 200]) def test_fingerprint_mismatch_raises_fr(self, *_): self.assert_fr("Error: Fingerprint mismatch", start_key_exchange, nick_to_pub_key("Alice"), 'Alice', *self.args) @mock.patch( 'builtins.input', side_effect=[ '', # Resend public key VALID_ECDHE_PUB_KEY, # Correct key 'Yes', # Fingerprint match '', # Resend contact data 'ff', # Invalid confirmation code blake2b(nick_to_pub_key('Alice'), digest_size=CONFIRM_CODE_LENGTH).hex() ]) @mock.patch('shutil.get_terminal_size', return_value=[200, 200]) @mock.patch('time.sleep', return_value=None) def test_successful_exchange(self, *_): self.assertIsNone( start_key_exchange(nick_to_pub_key("Alice"), 'Alice', *self.args)) contact = self.contact_list.get_contact_by_pub_key( nick_to_pub_key("Alice")) self.assertEqual(contact.onion_pub_key, nick_to_pub_key("Alice")) self.assertEqual(contact.nick, 'Alice') self.assertEqual(contact.kex_status, KEX_STATUS_VERIFIED) self.assertIsInstance(contact.tx_fingerprint, bytes) self.assertIsInstance(contact.rx_fingerprint, bytes) self.assertEqual(len(contact.tx_fingerprint), FINGERPRINT_LENGTH) self.assertEqual(len(contact.rx_fingerprint), FINGERPRINT_LENGTH) self.assertFalse(contact.log_messages) self.assertFalse(contact.file_reception) self.assertTrue(contact.notifications) self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 2) self.assertEqual(self.queues[RELAY_PACKET_QUEUE].qsize(), 2) cmd, account, tx_key, rx_key, tx_hek, rx_hek = self.queues[ KEY_MANAGEMENT_QUEUE].get() self.assertEqual(cmd, KDB_ADD_ENTRY_HEADER) self.assertEqual(account, nick_to_pub_key("Alice")) self.assertEqual(len(tx_key), SYMMETRIC_KEY_LENGTH) for key in [tx_key, rx_key, tx_hek, rx_hek]: self.assertIsInstance(key, bytes) self.assertEqual(len(key), SYMMETRIC_KEY_LENGTH) @mock.patch( 'builtins.input', side_effect=[ '', # Resend public key VALID_ECDHE_PUB_KEY, # Correct key KeyboardInterrupt, # Skip fingerprint verification '', # Manual proceed for warning message blake2b(nick_to_pub_key('Alice'), digest_size=CONFIRM_CODE_LENGTH).hex() ]) @mock.patch('time.sleep', return_value=None) @mock.patch('shutil.get_terminal_size', return_value=[200, 200]) def test_successful_exchange_skip_fingerprint_verification(self, *_): self.assertIsNone( start_key_exchange(nick_to_pub_key("Alice"), 'Alice', *self.args)) contact = self.contact_list.get_contact_by_pub_key( nick_to_pub_key("Alice")) self.assertEqual(contact.onion_pub_key, nick_to_pub_key("Alice")) self.assertEqual(contact.nick, 'Alice') self.assertEqual(contact.kex_status, KEX_STATUS_UNVERIFIED) @mock.patch( 'os.getrandom', side_effect=[SYMMETRIC_KEY_LENGTH * b'a', SYMMETRIC_KEY_LENGTH * b'a']) @mock.patch('builtins.input', side_effect=[ KeyboardInterrupt, VALID_ECDHE_PUB_KEY, 'Yes', blake2b(nick_to_pub_key('Alice'), digest_size=CONFIRM_CODE_LENGTH).hex() ]) @mock.patch('time.sleep', return_value=None) @mock.patch('shutil.get_terminal_size', return_value=[200, 200]) def test_successful_exchange_with_previous_key(self, *_): # Test caching of private key self.assert_fr("Key exchange interrupted.", start_key_exchange, nick_to_pub_key('Alice'), 'Alice', *self.args) alice = self.contact_list.get_contact_by_address_or_nick('Alice') self.assertEqual(alice.kex_status, KEX_STATUS_PENDING) # Test re-using private key self.assertIsNone( start_key_exchange(nick_to_pub_key('Alice'), 'Alice', *self.args)) self.assertIsNone(alice.tfc_private_key) self.assertEqual(alice.kex_status, KEX_STATUS_VERIFIED)
class TestProcessMessagePacket(TFCTestCase): def setUp(self) -> None: """Pre-test actions.""" self.unit_test_dir = cd_unit_test() self.msg = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean condimentum consectetur purus quis" " dapibus. Fusce venenatis lacus ut rhoncus faucibus. Cras sollicitudin commodo sapien, sed bibendu" "m velit maximus in. Aliquam ac metus risus. Sed cursus ornare luctus. Integer aliquet lectus id ma" "ssa blandit imperdiet. Ut sed massa eget quam facilisis rutrum. Mauris eget luctus nisl. Sed ut el" "it iaculis, faucibus lacus eget, sodales magna. Nunc sed commodo arcu. In hac habitasse platea dic" "tumst. Integer luctus aliquam justo, at vestibulum dolor iaculis ac. Etiam laoreet est eget odio r" "utrum, vel malesuada lorem rhoncus. Cras finibus in neque eu euismod. Nulla facilisi. Nunc nec ali" "quam quam, quis ullamcorper leo. Nunc egestas lectus eget est porttitor, in iaculis felis sceleris" "que. In sem elit, fringilla id viverra commodo, sagittis varius purus. Pellentesque rutrum loborti" "s neque a facilisis. Mauris id tortor placerat, aliquam dolor ac, venenatis arcu.") self.ts = datetime.now() self.master_key = MasterKey() self.settings = Settings(log_file_masking=True) self.file_name = f'{DIR_USER_DATA}{self.settings.software_operation}_logs' self.contact_list = ContactList(nicks=['Alice', 'Bob', 'Charlie', LOCAL_ID]) self.key_list = KeyList( nicks=['Alice', 'Bob', 'Charlie', LOCAL_ID]) self.group_list = GroupList( groups=['test_group']) self.packet_list = PacketList(contact_list=self.contact_list, settings=self.settings) self.window_list = WindowList(contact_list=self.contact_list, settings=self.settings, group_list=self.group_list, packet_list=self.packet_list) self.group_id = group_name_to_group_id('test_group') self.file_keys = dict() self.log_file = f'{DIR_USER_DATA}{self.settings.software_operation}_logs' self.tfc_log_database = MessageLog(self.log_file, self.master_key.master_key) self.group_list.get_group('test_group').log_messages = True self.args = (self.window_list, self.packet_list, self.contact_list, self.key_list, self.group_list, self.settings, self.file_keys, self.tfc_log_database) ensure_dir(DIR_USER_DATA) def tearDown(self) -> None: """Post-test actions.""" cleanup(self.unit_test_dir) # Invalid packets @mock.patch('time.sleep', return_value=None) def test_invalid_origin_header_raises_soft_error(self, _: Any) -> None: # Setup invalid_origin_header = b'e' packet = nick_to_pub_key('Alice') + invalid_origin_header + MESSAGE_LENGTH * b'm' # Test self.assert_se("Error: Received packet had an invalid origin-header.", process_message_packet, self.ts, packet, *self.args) @mock.patch('time.sleep', return_value=None) def test_masqueraded_command_raises_soft_error(self, _: Any) -> None: for origin_header in [ORIGIN_USER_HEADER, ORIGIN_CONTACT_HEADER]: # Setup packet = LOCAL_PUBKEY + origin_header + MESSAGE_LENGTH * b'm' # Test self.assert_se("Warning! Received packet masqueraded as a command.", process_message_packet, self.ts, packet, *self.args) # Private messages @mock.patch('time.sleep', return_value=None) def test_private_msg_from_contact(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, self.msg, origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice')) # Test for p in assembly_ct_list: self.assertIsNone(process_message_packet(self.ts, p, *self.args)) @mock.patch('time.sleep', return_value=None) def test_private_msg_from_user(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, self.msg, origin_header=ORIGIN_USER_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice')) # Test for p in assembly_ct_list: self.assertIsNone(process_message_packet(self.ts, p, *self.args)) # Whispered messages @mock.patch('time.sleep', return_value=None) def test_whisper_msg_from_contact(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, self.msg, origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), whisper_header=bool_to_bytes(True)) # Test for p in assembly_ct_list[:-1]: self.assertIsNone(process_message_packet(self.ts, p, *self.args)) for p in assembly_ct_list[-1:]: self.assert_se("Whisper message complete.", process_message_packet, self.ts, p, *self.args) @mock.patch('time.sleep', return_value=None) def test_whisper_msg_from_user(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, self.msg, origin_header=ORIGIN_USER_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), whisper_header=bool_to_bytes(True)) # Test for p in assembly_ct_list[:-1]: self.assertIsNone(process_message_packet(self.ts, p, *self.args)) for p in assembly_ct_list[-1:]: self.assert_se("Whisper message complete.", process_message_packet, self.ts, p, *self.args) @mock.patch('time.sleep', return_value=None) def test_empty_whisper_msg_from_user(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, '', origin_header=ORIGIN_USER_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), whisper_header=bool_to_bytes(True)) # Test for p in assembly_ct_list[:-1]: self.assertIsNone(process_message_packet(self.ts, p, *self.args)) for p in assembly_ct_list[-1:]: self.assert_se("Whisper message complete.", process_message_packet, self.ts, p, *self.args) # File key messages @mock.patch('time.sleep', return_value=None) def test_user_origin_raises_soft_error(self, _: Any) -> None: assembly_ct_list = assembly_packet_creator(MESSAGE, ' ', origin_header=ORIGIN_USER_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), message_header=FILE_KEY_HEADER) for p in assembly_ct_list[-1:]: self.assert_se("File key message from the user.", process_message_packet, self.ts, p, *self.args) @mock.patch('time.sleep', return_value=None) def test_invalid_file_key_data_raises_soft_error(self, _: Any) -> None: assembly_ct_list = assembly_packet_creator(MESSAGE, ' ', origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), message_header=FILE_KEY_HEADER) for p in assembly_ct_list[-1:]: self.assert_se("Error: Received an invalid file key message.", process_message_packet, self.ts, p, *self.args) @mock.patch('time.sleep', return_value=None) def test_too_large_file_key_data_raises_soft_error(self, _: Any) -> None: assembly_ct_list = assembly_packet_creator(MESSAGE, base64.b85encode(BLAKE2_DIGEST_LENGTH * b'a' + SYMMETRIC_KEY_LENGTH * b'b' + b'a').decode(), origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), message_header=FILE_KEY_HEADER) for p in assembly_ct_list[-1:]: self.assert_se("Error: Received an invalid file key message.", process_message_packet, self.ts, p, *self.args) @mock.patch('time.sleep', return_value=None) def test_valid_file_key_message(self, _: Any) -> None: assembly_ct_list = assembly_packet_creator(MESSAGE, base64.b85encode(BLAKE2_DIGEST_LENGTH * b'a' + SYMMETRIC_KEY_LENGTH * b'b').decode(), origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), message_header=FILE_KEY_HEADER) for p in assembly_ct_list[-1:]: self.assert_se("Received file decryption key from Alice", process_message_packet, self.ts, p, *self.args) # Group messages @mock.patch('time.sleep', return_value=None) def test_invalid_message_header_raises_soft_error(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, 'test_message', origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), message_header=b'Z') # Test self.assert_se("Error: Message from contact had an invalid header.", process_message_packet, self.ts, assembly_ct_list[0], *self.args) @mock.patch('time.sleep', return_value=None) def test_invalid_window_raises_soft_error(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, 'test_message', origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), group_id=self.group_id) self.group_list.get_group('test_group').group_id = GROUP_ID_LENGTH * b'a' # Test self.assert_se("Error: Received message to an unknown group.", process_message_packet, self.ts, assembly_ct_list[0], *self.args) @mock.patch('time.sleep', return_value=None) def test_invalid_message_raises_soft_error(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, ' ', origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), group_id=self.group_id, tamper_plaintext=True) # Test self.assert_se("Error: Received an invalid group message.", process_message_packet, self.ts, assembly_ct_list[0], *self.args) @mock.patch('time.sleep', return_value=None) def test_invalid_whisper_header_raises_soft_error(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, '', origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice'), whisper_header=b'', message_header=b'') # Test self.assert_se("Error: Message from contact had an invalid whisper header.", process_message_packet, self.ts, assembly_ct_list[0], *self.args) @mock.patch('time.sleep', return_value=None) def test_contact_not_in_group_raises_soft_error(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, 'test_message', origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, group_id=self.group_id, onion_pub_key=nick_to_pub_key('Charlie')) # Test self.assert_se("Error: Account is not a member of the group.", process_message_packet, self.ts, assembly_ct_list[0], *self.args) @mock.patch('time.sleep', return_value=None) def test_normal_group_msg_from_contact(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, self.msg, origin_header=ORIGIN_CONTACT_HEADER, group_id=self.group_id, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice')) for p in assembly_ct_list: self.assertIsNone(process_message_packet(self.ts, p, *self.args)) @mock.patch('time.sleep', return_value=None) def test_normal_group_msg_from_user(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(MESSAGE, self.msg, origin_header=ORIGIN_USER_HEADER, group_id=self.group_id, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice')) for p in assembly_ct_list: self.assertIsNone(process_message_packet(self.ts, p, *self.args)) # Files @mock.patch('time.sleep', return_value=None) def test_file(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(FILE, origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice')) # Test for p in assembly_ct_list[:-1]: self.assertIsNone(process_message_packet(self.ts, p, *self.args)) for p in assembly_ct_list[-1:]: self.assert_se("File storage complete.", process_message_packet, self.ts, p, *self.args) @mock.patch('time.sleep', return_value=None) def test_file_when_reception_is_disabled(self, _: Any) -> None: # Setup assembly_ct_list = assembly_packet_creator(FILE, origin_header=ORIGIN_CONTACT_HEADER, encrypt_packet=True, onion_pub_key=nick_to_pub_key('Alice')) self.contact_list.get_contact_by_pub_key(nick_to_pub_key('Alice')).file_reception = False # Test self.assert_se("Alert! File transmission from Alice but reception is disabled.", process_message_packet, self.ts, assembly_ct_list[0], *self.args)
class TestTxWindow(TFCTestCase): def setUp(self) -> None: """Pre-test actions.""" self.contact_list = ContactList(['Alice', 'Bob', LOCAL_ID]) self.group_list = GroupList(groups=['test_group', 'test_group_2']) self.window = TxWindow(self.contact_list, self.group_list) self.window.group = self.group_list.get_group('test_group') self.window.type = WIN_TYPE_GROUP self.settings = Settings() self.queues = gen_queue_dict() self.onion_service = OnionService() self.gateway = Gateway() self.args = self.settings, self.queues, self.onion_service, self.gateway def tearDown(self) -> None: """Post-test actions.""" tear_queues(self.queues) def test_window_iterates_over_contacts(self) -> None: # Setup self.window.window_contacts = self.contact_list.contacts # Test for c in self.window: self.assertIsInstance(c, Contact) def test_len_returns_number_of_contacts_in_window(self) -> None: # Setup self.window.window_contacts = [ self.contact_list.get_contact_by_pub_key(nick_to_pub_key('Alice')), self.contact_list.get_contact_by_pub_key(nick_to_pub_key('Bob')) ] # Test self.assertEqual(len(self.window), 2) def test_group_window_change_during_traffic_masking_raises_soft_error( self) -> None: # Setup self.settings.traffic_masking = True self.window.uid = 'test_group' # Test self.assert_se("Error: Can't change window during traffic masking.", self.window.select_tx_window, *self.args, selection='test_group_2', cmd=True) def test_contact_window_change_during_traffic_masking_raises_soft_error( self) -> None: # Setup self.settings.traffic_masking = True self.window.uid = nick_to_pub_key("Alice") # Test self.assert_se("Error: Can't change window during traffic masking.", self.window.select_tx_window, *self.args, selection=nick_to_onion_address("Bob"), cmd=True) def test_contact_window_reload_during_traffic_masking(self) -> None: # Setup self.settings.traffic_masking = True self.window.uid = nick_to_pub_key("Alice") # Test self.assertIsNone( self.window.select_tx_window( *self.args, selection=nick_to_onion_address("Alice"), cmd=True)) self.assertEqual(self.window.uid, nick_to_pub_key("Alice")) def test_group_window_reload_during_traffic_masking(self) -> None: # Setup self.settings.traffic_masking = True self.window.name = 'test_group' self.window.uid = group_name_to_group_id('test_group') # Test self.assertIsNone( self.window.select_tx_window(*self.args, selection='test_group', cmd=True)) self.assertEqual(self.window.uid, group_name_to_group_id('test_group')) def test_invalid_selection_raises_soft_error(self) -> None: # Setup self.window.uid = nick_to_pub_key("Alice") # Test self.assert_se("Error: No contact/group was found.", self.window.select_tx_window, *self.args, selection=nick_to_onion_address("Charlie"), cmd=True) @mock.patch('builtins.input', return_value=nick_to_onion_address("Bob")) def test_window_selection_during_traffic_masking(self, *_: Any) -> None: # Setup self.settings.traffic_masking = True self.window.uid = None # Test self.assertIsNone(self.window.select_tx_window(*self.args)) self.assertEqual(self.queues[WINDOW_SELECT_QUEUE].qsize(), 1) @mock.patch('builtins.input', return_value=nick_to_onion_address("Bob")) def test_contact_window_selection_from_input(self, *_: Any) -> None: # Setup self.window.uid = None # Test self.assertIsNone(self.window.select_tx_window(*self.args)) self.assertEqual(self.window.uid, nick_to_pub_key("Bob")) def test_group_window_selection_from_command(self) -> None: # Setup self.window.uid = None self.assertIsNone( self.window.select_tx_window(*self.args, selection='test_group', cmd=True)) self.assertEqual(self.window.uid, group_name_to_group_id('test_group')) def test_deselect_window(self) -> None: # Setup self.window.window_contacts = self.contact_list.contacts self.window.contact = self.contact_list.get_contact_by_address_or_nick( "Bob") self.window.name = 'Bob' self.window.type = WIN_TYPE_CONTACT self.window.uid = nick_to_pub_key("Bob") # Test self.assertIsNone(self.window.deselect()) self.assertIsNone(self.window.contact) self.assertEqual(self.window.name, '') self.assertEqual(self.window.type, '') self.assertEqual(self.window.uid, b'') def test_is_selected(self) -> None: self.window.name = '' self.assertFalse(self.window.is_selected()) self.window.name = nick_to_pub_key("Bob") self.assertTrue(self.window.is_selected()) def test_update_log_messages_for_contact(self) -> None: # Setup self.window.type = WIN_TYPE_CONTACT self.window.log_messages = None self.window.contact = self.contact_list.get_contact_by_address_or_nick( 'Alice') self.window.contact.log_messages = False # Test self.assertIsNone(self.window.update_log_messages()) self.assertFalse(self.window.log_messages) def test_update_log_messages_for_group(self) -> None: # Setup self.window.type = WIN_TYPE_GROUP self.window.log_messages = None self.window.group = self.group_list.get_group('test_group') self.window.group.log_messages = False # Test self.assertIsNone(self.window.update_log_messages()) self.assertFalse(self.window.log_messages) def test_update_group_win_members_if_group_is_available(self) -> None: # Setup self.window.window_contacts = [] self.window.group = None self.window.group_id = group_name_to_group_id('test_group') self.window.name = 'test_group' self.window.type = WIN_TYPE_GROUP # Test self.assertIsNone(self.window.update_window(self.group_list)) self.assertEqual(self.window.group, self.group_list.get_group('test_group')) self.assertEqual(self.window.window_contacts, self.window.group.members) def test_window_contact_is_reloaded_when_contact_is_active(self) -> None: # Setup self.window.type = WIN_TYPE_CONTACT self.window.contact = create_contact('Alice') self.window.window_contacts = [self.window.contact] self.assertIsNot( self.window.contact, self.window.contact_list.get_contact_by_pub_key( nick_to_pub_key('Alice'))) self.assertIsNot( self.window.window_contacts[0], self.window.contact_list.get_contact_by_pub_key( nick_to_pub_key('Alice'))) # Test self.assertIsNone(self.window.update_window(self.group_list)) self.assertIs( self.window.contact, self.window.contact_list.get_contact_by_pub_key( nick_to_pub_key('Alice'))) self.assertIs( self.window.window_contacts[0], self.window.contact_list.get_contact_by_pub_key( nick_to_pub_key('Alice'))) def test_deactivate_window_if_group_is_not_available(self) -> None: # Setup self.window.window_contacts = [] self.window.group = None self.window.name = 'test_group_3' self.window.type = WIN_TYPE_GROUP # Test self.assertIsNone(self.window.update_window(self.group_list)) self.assertIsNone(self.window.contact) self.assertEqual(self.window.name, '') self.assertEqual(self.window.type, '') self.assertEqual(self.window.uid, b'') @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=[ 'Alice', VALID_ECDHE_PUB_KEY, 'yes', blake2b(nick_to_pub_key('Alice'), digest_size=CONFIRM_CODE_LENGTH).hex() ]) @mock.patch('shutil.get_terminal_size', return_value=[200, 200]) def test_selecting_pending_contact_starts_key_exchange(self, *_: Any) -> None: # Setup alice = self.contact_list.get_contact_by_address_or_nick('Alice') bob = self.contact_list.get_contact_by_address_or_nick('Bob') alice.kex_status = KEX_STATUS_PENDING bob.kex_status = KEX_STATUS_PENDING # Test self.assertIsNone(self.window.select_tx_window(*self.args)) self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 2) self.assertEqual(self.queues[WINDOW_SELECT_QUEUE].qsize(), 0) self.assertEqual(alice.kex_status, KEX_STATUS_VERIFIED) @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=[ '/add', nick_to_onion_address('Alice'), 'Alice', '', VALID_ECDHE_PUB_KEY, 'yes', blake2b(nick_to_pub_key('Alice'), digest_size=CONFIRM_CODE_LENGTH).hex() ]) @mock.patch('shutil.get_terminal_size', return_value=[200, 200]) def test_adding_new_contact_from_contact_selection(self, *_: Any) -> None: # Setup alice = self.contact_list.get_contact_by_address_or_nick('Alice') alice.kex_status = KEX_STATUS_PENDING # Test self.assert_se('New contact added.', self.window.select_tx_window, *self.args) self.assertEqual(self.queues[COMMAND_PACKET_QUEUE].qsize(), 1) self.assertEqual(self.queues[WINDOW_SELECT_QUEUE].qsize(), 0) self.assertEqual(alice.kex_status, KEX_STATUS_VERIFIED) @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=['/rm ']) def test_missing_account_when_removing_raises_soft_error(self, *_: Any) -> None: self.assert_se("Error: No account specified.", self.window.select_tx_window, *self.args) @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=['/rm Charlie', 'yes']) def test_unknown_account_when_removing_raises_soft_error(self, *_: Any) -> None: self.assert_se("Error: Unknown contact 'Charlie'.", self.window.select_tx_window, *self.args) @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=['/rm Alice', 'no']) def test_abort_removal_of_contact_form_contact_selection(self, *_: Any) -> None: self.assert_se("Removal of contact aborted.", self.window.select_tx_window, *self.args) @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=['/rm Alice', 'yes']) def test_removing_pending_contact_from_contact_selection(self, *_: Any) -> None: self.assert_se("Removed contact 'Alice'.", self.window.select_tx_window, *self.args) @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=['/connect', b'a'.hex()]) def test_sending_onion_service_data_from_contact_selection( self, *_: Any) -> None: self.assertIsNone(self.window.select_tx_window(*self.args)) self.assertEqual(len(self.gateway.packets), 1) @mock.patch('time.sleep', return_value=None) @mock.patch('builtins.input', side_effect=['/help']) def test_invalid_command_raises_soft_error(self, *_: Any) -> None: self.assert_se("Error: Invalid command.", self.window.select_tx_window, *self.args)