def test_storing_and_loading_of_groups(self): self.group_list.store_groups() self.assertTrue(os.path.isfile(self.file_name)) self.assertEqual( os.path.getsize(self.file_name), XCHACHA20_NONCE_LENGTH + GROUP_DB_HEADER_LENGTH + self.settings.max_number_of_groups * self.single_member_data_len + POLY1305_TAG_LENGTH) # Reduce setting values from 20 to 10 self.settings.max_number_of_groups = 10 self.settings.max_number_of_group_members = 10 group_list2 = GroupList(self.master_key, self.settings, self.contact_list) self.assertEqual(len(group_list2), 11) # Check that `_load_groups()` increased setting values back to 20 so it fits the 11 groups self.assertEqual(self.settings.max_number_of_groups, 20) self.assertEqual(self.settings.max_number_of_group_members, 20) # Check that removed contact from contact list updates group self.contact_list.remove_contact_by_address_or_nick('Alice') group_list3 = GroupList(self.master_key, self.settings, self.contact_list) self.assertEqual(len(group_list3.get_group('test_group_1').members), 10)
def setUp(self): self.unittest_dir = cd_unittest() self.master_key = MasterKey() self.settings = Settings() self.file_name = f'{DIR_USER_DATA}{self.settings.software_operation}_groups' self.contact_list = ContactList(self.master_key, self.settings) self.group_list = GroupList(self.master_key, self.settings, self.contact_list) self.nicks = [ 'Alice', 'Bob', 'Charlie', 'David', 'Eric', 'Fido', 'Guido', 'Heidi', 'Ivan', 'Joana', 'Karol' ] self.group_names = [ 'test_group_1', 'test_group_2', 'test_group_3', 'test_group_4', 'test_group_5', 'test_group_6', 'test_group_7', 'test_group_8', 'test_group_9', 'test_group_10', 'test_group_11' ] members = list(map(create_contact, self.nicks)) self.contact_list.contacts = members self.group_list.groups = \ [Group(name =name, group_id =group_name_to_group_id(name), log_messages =False, notifications=False, members =members, settings =self.settings, store_groups =self.group_list.store_groups) for name in self.group_names] self.single_member_data_len = ( GROUP_STATIC_LENGTH + self.settings.max_number_of_group_members * ONION_SERVICE_PUBLIC_KEY_LENGTH)
def test_load_of_modified_database_raises_critical_error(self) -> None: self.group_list.store_groups() # Test reading works normally self.assertIsInstance(GroupList(self.master_key, self.settings, self.contact_list), GroupList) # Test loading of the tampered database raises CriticalError tamper_file(self.file_name, tamper_size=1) with self.assertRaises(SystemExit): GroupList(self.master_key, self.settings, self.contact_list)
def test_database_size(self): self.assertTrue(os.path.isfile(f'{DIR_USER_DATA}ut_groups')) self.assertEqual(os.path.getsize(f'{DIR_USER_DATA}ut_groups'), XSALSA20_NONCE_LEN + GROUP_DB_HEADER_LEN + self.settings.max_number_of_groups * self.single_member_data + POLY1305_TAG_LEN) self.settings.max_number_of_groups = 10 self.settings.max_number_of_group_members = 10 group_list2 = GroupList(self.master_key, self.settings, self.contact_list) self.assertEqual(len(group_list2), 11) # Check that load_groups() function increases setting values with larger db self.assertEqual(self.settings.max_number_of_groups, 20) self.assertEqual(self.settings.max_number_of_group_members, 20) # Check that removed contact from contact list updates group self.contact_list.remove_contact('Alice') group_list3 = GroupList(self.master_key, self.settings, self.contact_list) self.assertEqual(len(group_list3.get_group('testgroup_1').members), 10) group_list4 = GroupList(self.master_key, self.settings, self.contact_list) self.assertEqual(len(group_list4.get_group('testgroup_2').members), 10)
def setUp(self): self.master_key = MasterKey() self.settings = Settings() self.contact_list = ContactList(self.master_key, self.settings) self.group_list = GroupList(self.master_key, self.settings, self.contact_list) members = [create_contact(n) for n in ['Alice', 'Bob', 'Charlie', 'David', 'Eric', 'Fido', 'Guido', 'Heidi', 'Ivan', 'Joana', 'Karol']] self.contact_list.contacts = members groups = [Group(n, False, False, members, self.settings, self.group_list.store_groups) for n in ['testgroup_1', 'testgroup_2', 'testgroup_3', 'testgroup_4', 'testgroup_5', 'testgroup_6', 'testgroup_7', 'testgroup_8', 'testgroup_9', 'testgroup_10', 'testgroup_11']] self.group_list.groups = groups self.group_list.store_groups() self.single_member_data = (PADDED_UTF32_STR_LEN + (2 * BOOLEAN_SETTING_LEN) + (self.settings.max_number_of_group_members * PADDED_UTF32_STR_LEN))
def test_invalid_content_raises_critical_error(self) -> None: # Setup invalid_data = b'a' pt_bytes = self.group_list._generate_group_db_header() pt_bytes += b''.join([g.serialize_g() for g in (self.group_list.groups + self.group_list._dummy_groups())]) ct_bytes = encrypt_and_sign(pt_bytes + invalid_data, self.master_key.master_key) ensure_dir(DIR_USER_DATA) with open(self.file_name, 'wb+') as f: f.write(ct_bytes) # Test with self.assertRaises(SystemExit): GroupList(self.master_key, self.settings, self.contact_list)
class TestGroupList(TFCTestCase): def setUp(self): self.unittest_dir = cd_unittest() self.master_key = MasterKey() self.settings = Settings() self.file_name = f'{DIR_USER_DATA}{self.settings.software_operation}_groups' self.contact_list = ContactList(self.master_key, self.settings) self.group_list = GroupList(self.master_key, self.settings, self.contact_list) self.nicks = [ 'Alice', 'Bob', 'Charlie', 'David', 'Eric', 'Fido', 'Guido', 'Heidi', 'Ivan', 'Joana', 'Karol' ] self.group_names = [ 'test_group_1', 'test_group_2', 'test_group_3', 'test_group_4', 'test_group_5', 'test_group_6', 'test_group_7', 'test_group_8', 'test_group_9', 'test_group_10', 'test_group_11' ] members = list(map(create_contact, self.nicks)) self.contact_list.contacts = members self.group_list.groups = \ [Group(name =name, group_id =group_name_to_group_id(name), log_messages =False, notifications=False, members =members, settings =self.settings, store_groups =self.group_list.store_groups) for name in self.group_names] self.single_member_data_len = ( GROUP_STATIC_LENGTH + self.settings.max_number_of_group_members * ONION_SERVICE_PUBLIC_KEY_LENGTH) def tearDown(self): cleanup(self.unittest_dir) def test_group_list_iterates_over_group_objects(self): for g in self.group_list: self.assertIsInstance(g, Group) def test_len_returns_the_number_of_groups(self): self.assertEqual(len(self.group_list), len(self.group_names)) def test_storing_and_loading_of_groups(self): self.group_list.store_groups() self.assertTrue(os.path.isfile(self.file_name)) self.assertEqual( os.path.getsize(self.file_name), XCHACHA20_NONCE_LENGTH + GROUP_DB_HEADER_LENGTH + self.settings.max_number_of_groups * self.single_member_data_len + POLY1305_TAG_LENGTH) # Reduce setting values from 20 to 10 self.settings.max_number_of_groups = 10 self.settings.max_number_of_group_members = 10 group_list2 = GroupList(self.master_key, self.settings, self.contact_list) self.assertEqual(len(group_list2), 11) # Check that `_load_groups()` increased setting values back to 20 so it fits the 11 groups self.assertEqual(self.settings.max_number_of_groups, 20) self.assertEqual(self.settings.max_number_of_group_members, 20) # Check that removed contact from contact list updates group self.contact_list.remove_contact_by_address_or_nick('Alice') group_list3 = GroupList(self.master_key, self.settings, self.contact_list) self.assertEqual(len(group_list3.get_group('test_group_1').members), 10) def test_invalid_content_raises_critical_error(self): # Setup invalid_data = b'a' pt_bytes = self.group_list._generate_group_db_header() pt_bytes += b''.join([ g.serialize_g() for g in (self.group_list.groups + self.group_list._dummy_groups()) ]) ct_bytes = encrypt_and_sign(pt_bytes + invalid_data, self.master_key.master_key) ensure_dir(DIR_USER_DATA) with open(self.file_name, 'wb+') as f: f.write(ct_bytes) # Test with self.assertRaises(SystemExit): GroupList(self.master_key, self.settings, self.contact_list) def test_load_of_modified_database_raises_critical_error(self): self.group_list.store_groups() # Test reading works normally self.assertIsInstance( GroupList(self.master_key, self.settings, self.contact_list), GroupList) # Test loading of the tampered database raises CriticalError tamper_file(self.file_name, tamper_size=1) with self.assertRaises(SystemExit): GroupList(self.master_key, self.settings, self.contact_list) def test_check_db_settings(self): self.assertFalse( self.group_list._check_db_settings( number_of_actual_groups=self.settings.max_number_of_groups, members_in_largest_group=self.settings. max_number_of_group_members)) self.assertTrue( self.group_list._check_db_settings( number_of_actual_groups=self.settings.max_number_of_groups + 1, members_in_largest_group=self.settings. max_number_of_group_members)) self.assertTrue( self.group_list._check_db_settings( number_of_actual_groups=self.settings.max_number_of_groups, members_in_largest_group=self.settings. max_number_of_group_members + 1)) def test_generate_group_db_header(self): header = self.group_list._generate_group_db_header() self.assertEqual(len(header), GROUP_DB_HEADER_LENGTH) self.assertIsInstance(header, bytes) def test_generate_dummy_group(self): dummy_group = self.group_list._generate_dummy_group() self.assertIsInstance(dummy_group, Group) self.assertEqual(len(dummy_group.serialize_g()), self.single_member_data_len) def test_dummy_groups(self): dummies = self.group_list._dummy_groups() self.assertEqual( len(dummies), self.settings.max_number_of_contacts - len(self.nicks)) for g in dummies: self.assertIsInstance(g, Group) def test_add_group(self): members = [create_contact('Laura')] self.group_list.add_group('test_group_12', bytes(GROUP_ID_LENGTH), False, False, members) self.group_list.add_group('test_group_12', bytes(GROUP_ID_LENGTH), False, True, members) self.assertTrue( self.group_list.get_group('test_group_12').notifications) self.assertEqual(len(self.group_list), len(self.group_names) + 1) def test_remove_group_by_name(self): self.assertEqual(len(self.group_list), len(self.group_names)) # Remove non-existing group self.assertIsNone( self.group_list.remove_group_by_name('test_group_12')) self.assertEqual(len(self.group_list), len(self.group_names)) # Remove existing group self.assertIsNone( self.group_list.remove_group_by_name('test_group_11')) self.assertEqual(len(self.group_list), len(self.group_names) - 1) def test_remove_group_by_id(self): self.assertEqual(len(self.group_list), len(self.group_names)) # Remove non-existing group self.assertIsNone( self.group_list.remove_group_by_id( group_name_to_group_id('test_group_12'))) self.assertEqual(len(self.group_list), len(self.group_names)) # Remove existing group self.assertIsNone( self.group_list.remove_group_by_id( group_name_to_group_id('test_group_11'))) self.assertEqual(len(self.group_list), len(self.group_names) - 1) def test_get_group(self): self.assertEqual( self.group_list.get_group('test_group_3').name, 'test_group_3') def test_get_group_by_id(self): members = [create_contact('Laura')] group_id = os.urandom(GROUP_ID_LENGTH) self.group_list.add_group('test_group_12', group_id, False, False, members) self.assertEqual( self.group_list.get_group_by_id(group_id).name, 'test_group_12') def test_get_list_of_group_names(self): self.assertEqual(self.group_list.get_list_of_group_names(), self.group_names) def test_get_list_of_group_ids(self): self.assertEqual(self.group_list.get_list_of_group_ids(), list(map(group_name_to_group_id, self.group_names))) def test_get_list_of_hr_group_ids(self): self.assertEqual(self.group_list.get_list_of_hr_group_ids(), [ b58encode(gid) for gid in list(map(group_name_to_group_id, self.group_names)) ]) def test_get_group_members(self): members = self.group_list.get_group_members( group_name_to_group_id('test_group_1')) for c in members: self.assertIsInstance(c, Contact) def test_has_group(self): self.assertTrue(self.group_list.has_group('test_group_11')) self.assertFalse(self.group_list.has_group('test_group_12')) def test_has_group_id(self): members = [create_contact('Laura')] group_id = os.urandom(GROUP_ID_LENGTH) self.assertFalse(self.group_list.has_group_id(group_id)) self.group_list.add_group('test_group_12', group_id, False, False, members) self.assertTrue(self.group_list.has_group_id(group_id)) def test_largest_group(self): self.assertEqual(self.group_list.largest_group(), len(self.nicks)) def test_print_group(self): self.group_list.get_group("test_group_1").name = "group" self.group_list.get_group("test_group_2").log_messages = True self.group_list.get_group("test_group_3").notifications = True self.group_list.get_group("test_group_4").log_messages = True self.group_list.get_group("test_group_4").notifications = True self.group_list.get_group("test_group_5").members = [] self.group_list.get_group("test_group_6").members = list( map(create_contact, ['Alice', 'Bob', 'Charlie', 'David', 'Eric', 'Fido'])) self.assert_prints( """\ Group Group ID Logging Notify Members ──────────────────────────────────────────────────────────────────────────────── group 2drs4c4VcDdrP No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol test_group_2 2dnGTyhkThmPi Yes No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol test_group_3 2df7s3LZhwLDw No Yes Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol test_group_4 2djy3XwUQVR8q Yes Yes Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol test_group_5 2dvbcgnjiLLMo No No <Empty group> test_group_6 2dwBRWAqWKHWv No No Alice, Bob, Charlie, David, Eric, Fido test_group_7 2eDPg5BAM6qF4 No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol test_group_8 2dqdayy5TJKcf No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol test_group_9 2e45bLYvSX3C8 No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol test_group_10 2dgkncX9xRibh No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol test_group_11 2e6vAGmHmSEEJ No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol """, self.group_list.print_groups)
def test_class(self): # Setup master_key = MasterKey() settings = Settings() contact_list = ContactList(master_key, settings) group_list = GroupList(master_key, settings, contact_list) members = [ create_contact(n) for n in [ 'Alice', 'Bob', 'Charlie', 'David', 'Eric', 'Fido', 'Gunter', 'Heidi', 'Ivan', 'Joana', 'Karol' ] ] contact_list.contacts = members groups = [ Group(n, False, False, members, settings, group_list.store_groups()) for n in [ 'testgroup_1', 'testgroup_2', 'testgroup3', 'testgroup_4', 'testgroup_5', 'testgroup_6', 'testgroup_7', 'testgroup8', 'testgroup_9', 'testgroup_10', 'testgroup_11' ] ] group_list.groups = groups group_list.store_groups() # Test for g in group_list: self.assertIsInstance(g, Group) self.assertEqual(len(group_list), 11) self.assertTrue(os.path.isfile(f'{DIR_USER_DATA}/ut_groups')) self.assertEqual(os.path.getsize(f'{DIR_USER_DATA}/ut_groups'), 24 + 32 + 20 * (1024 + 2 + (20 * 1024)) + 16) settings.m_number_of_groups = 10 settings.m_members_in_group = 10 group_list2 = GroupList(master_key, settings, contact_list) self.assertEqual(len(group_list2), 11) self.assertEqual(settings.m_number_of_groups, 20) self.assertEqual(settings.m_members_in_group, 20) bytestring = group_list2.generate_header() self.assertEqual(len(bytestring), 32) self.assertIsInstance(bytestring, bytes) dg_bytestring = group_list2.generate_dummy_group() self.assertEqual(len(dg_bytestring), (1024 + 2 + (20 * 1024))) self.assertIsInstance(dg_bytestring, bytes) members.append(create_contact('Laura')) group_list2.add_group('testgroup_12', False, False, members) group_list2.add_group('testgroup_12', False, True, members) self.assertTrue(group_list2.get_group('testgroup_12').notifications) self.assertEqual(len(group_list2), 12) self.assertEqual(group_list2.largest_group(), 12) g_names = [ 'testgroup_1', 'testgroup_2', 'testgroup3', 'testgroup_4', 'testgroup_5', 'testgroup_6', 'testgroup_7', 'testgroup8', 'testgroup_9', 'testgroup_10', 'testgroup_11', 'testgroup_12' ] self.assertEqual(group_list2.get_list_of_group_names(), g_names) g_o = group_list2.get_group('testgroup_1') self.assertIsInstance(g_o, Group) self.assertEqual(g_o.name, 'testgroup_1') self.assertTrue(group_list2.has_group('testgroup_12')) self.assertFalse(group_list2.has_group('testgroup_13')) self.assertTrue(group_list2.has_groups(), True) members = group_list2.get_group_members('testgroup_1') for c in members: self.assertIsInstance(c, Contact) self.assertEqual(len(group_list2), 12) group_list2.remove_group('testgroup_13') self.assertEqual(len(group_list2), 12) group_list2.remove_group('testgroup_12') self.assertEqual(len(group_list2), 11) self.assertIsNone(group_list2.print_groups()) # Teardown cleanup()
def main() -> None: """Load persistent data and launch the Transmitter/Receiver Program. This function decrypts user data from databases and launches processes for Transmitter or Receiver Program. It then monitors the EXIT_QUEUE for EXIT/WIPE signals and each process in case one of them dies. If you're reading this code to get the big picture on how TFC works, start by looking at the loop functions below, defined as the target for each process, from top to bottom: From `input_loop` process, you can see how the Transmitter Program processes a message or command from the user, creates assembly packets for a message/file/command, and how those are eventually pushed into a multiprocessing queue, from where they are loaded by the `sender_loop`. The `sender_loop` process encrypts outgoing assembly packets, and outputs the encrypted datagrams to the Networked Computer. The process also sends assembly packets to the `log_writer_loop`. The `log_writer_loop` process filters out non-message assembly packets and if logging for contact is enabled, stores the message assembly packet into an encrypted log database. The `noise_loop` processes are used to provide the `sender_loop` an interface identical to that of the `input_loop`. The `sender_loop` uses the interface to load noise packets/commands when traffic masking is enabled. Refer to the file `relay.py` to see how the Relay Program on Networked Computer manages datagrams between the network and Source/Destination Computer. In Receiver Program (also launched by this file), the `gateway_loop` process acts as a buffer for incoming datagrams. This buffer is consumed by the `receiver_loop` process that organizes datagrams loaded from the buffer into a set of queues depending on datagram type. Finally, the `output_loop` process loads and processes datagrams from the queues in the order of priority. """ working_dir = f'{os.getenv("HOME")}/{DIR_TFC}' ensure_dir(working_dir) os.chdir(working_dir) operation, local_test, data_diode_sockets = process_arguments() check_kernel_version() check_kernel_entropy() print_title(operation) master_key = MasterKey( operation, local_test) gateway = Gateway( operation, local_test, data_diode_sockets) settings = Settings( master_key, operation, local_test) contact_list = ContactList(master_key, settings) key_list = KeyList( master_key, settings) group_list = GroupList( master_key, settings, contact_list) if settings.software_operation == TX: onion_service = OnionService(master_key) queues = {MESSAGE_PACKET_QUEUE: Queue(), # Standard messages COMMAND_PACKET_QUEUE: Queue(), # Standard commands TM_MESSAGE_PACKET_QUEUE: Queue(), # Traffic masking messages TM_FILE_PACKET_QUEUE: Queue(), # Traffic masking files TM_COMMAND_PACKET_QUEUE: Queue(), # Traffic masking commands TM_NOISE_PACKET_QUEUE: Queue(), # Traffic masking noise packets TM_NOISE_COMMAND_QUEUE: Queue(), # Traffic masking noise commands RELAY_PACKET_QUEUE: Queue(), # Unencrypted datagrams to Networked Computer LOG_PACKET_QUEUE: Queue(), # `log_writer_loop` assembly packets to be logged LOG_SETTING_QUEUE: Queue(), # `log_writer_loop` logging state management between noise packets TRAFFIC_MASKING_QUEUE: Queue(), # `log_writer_loop` traffic masking setting management commands LOGFILE_MASKING_QUEUE: Queue(), # `log_writer_loop` logfile masking setting management commands KEY_MANAGEMENT_QUEUE: Queue(), # `sender_loop` key database management commands SENDER_MODE_QUEUE: Queue(), # `sender_loop` default/traffic masking mode switch commands WINDOW_SELECT_QUEUE: Queue(), # `sender_loop` window selection commands during traffic masking EXIT_QUEUE: Queue() # EXIT/WIPE signal from `input_loop` to `main` } # type: Dict[bytes, Queue] process_list = [Process(target=input_loop, args=(queues, settings, gateway, contact_list, group_list, master_key, onion_service, sys.stdin.fileno())), Process(target=sender_loop, args=(queues, settings, gateway, key_list)), Process(target=log_writer_loop, args=(queues, settings)), Process(target=noise_loop, args=(queues, contact_list)), Process(target=noise_loop, args=(queues,))] else: queues = {GATEWAY_QUEUE: Queue(), # Buffer for incoming datagrams LOCAL_KEY_DATAGRAM_HEADER: Queue(), # Local key datagrams MESSAGE_DATAGRAM_HEADER: Queue(), # Message datagrams FILE_DATAGRAM_HEADER: Queue(), # File datagrams COMMAND_DATAGRAM_HEADER: Queue(), # Command datagrams EXIT_QUEUE: Queue() # EXIT/WIPE signal from `output_loop` to `main` } process_list = [Process(target=gateway_loop, args=(queues, gateway)), Process(target=receiver_loop, args=(queues, gateway)), Process(target=output_loop, args=(queues, gateway, settings, contact_list, key_list, group_list, master_key, sys.stdin.fileno()))] for p in process_list: p.start() monitor_processes(process_list, settings.software_operation, queues)
def main() -> None: """Derive master key, decrypt databases and initialize processes.""" os.chdir(sys.path[0]) init_entropy() operation, local_test, dd_sockets = process_arguments() clear_screen() c_print("TFC", head=1, tail=1) master_key = MasterKey(operation, local_test) settings = Settings(master_key, operation, local_test, dd_sockets) contact_list = ContactList(master_key, settings) key_list = KeyList(master_key, settings) group_list = GroupList(master_key, settings, contact_list) gateway = Gateway(settings) process_list = [] if settings.software_operation == 'tx': queues = { MESSAGE_PACKET_QUEUE: Queue(), FILE_PACKET_QUEUE: Queue(), COMMAND_PACKET_QUEUE: Queue(), LOG_PACKET_QUEUE: Queue(), NOISE_PACKET_QUEUE: Queue(), NOISE_COMMAND_QUEUE: Queue(), KEY_MANAGEMENT_QUEUE: Queue(), WINDOW_SELECT_QUEUE: Queue() } if settings.session_trickle: np_filler = Process(target=noise_process, args=(P_N_HEADER, queues[NOISE_PACKET_QUEUE], contact_list)) nc_filler = Process(target=noise_process, args=(C_N_HEADER, queues[NOISE_COMMAND_QUEUE])) process_list.extend([np_filler, nc_filler]) for p in [np_filler, nc_filler]: p.start() while any([ q.qsize() < 1000 for q in [queues[NOISE_PACKET_QUEUE], queues[NOISE_COMMAND_QUEUE]] ]): time.sleep(0.1) sender_process = Process(target=sender_loop, args=(settings, queues, gateway, key_list)) input_process = Process(target=tx_loop, args=(settings, queues, gateway, contact_list, group_list, master_key, sys.stdin.fileno())) log_process = Process(target=log_writer, args=(queues[LOG_PACKET_QUEUE], )) process_list.extend([sender_process, input_process, log_process]) for p in [sender_process, input_process, log_process]: p.start() elif settings.software_operation == 'rx': queues = { LOCAL_KEY_PACKET_HEADER: Queue(), PUBLIC_KEY_PACKET_HEADER: Queue(), MESSAGE_PACKET_HEADER: Queue(), COMMAND_PACKET_HEADER: Queue(), IMPORTED_FILE_CT_HEADER: Queue(), GATEWAY_QUEUE: Queue() } gateway_process = Process(target=gw_incoming, args=(gateway, queues[GATEWAY_QUEUE])) receiver_process = Process(target=receiver_loop, args=(settings, queues)) output_process = Process(target=rx_loop, args=(settings, queues, contact_list, key_list, group_list, master_key, sys.stdin.fileno())) process_list.extend( [gateway_process, receiver_process, output_process]) for p in [gateway_process, receiver_process, output_process]: p.start() while True: try: time.sleep(0.1) if not all([p.is_alive() for p in process_list]): for p in process_list: p.terminate() exit() except (EOFError, KeyboardInterrupt): pass
class TestGroupList(TFCTestCase): def setUp(self): self.master_key = MasterKey() self.settings = Settings() self.contact_list = ContactList(self.master_key, self.settings) self.group_list = GroupList(self.master_key, self.settings, self.contact_list) members = [create_contact(n) for n in ['Alice', 'Bob', 'Charlie', 'David', 'Eric', 'Fido', 'Guido', 'Heidi', 'Ivan', 'Joana', 'Karol']] self.contact_list.contacts = members groups = [Group(n, False, False, members, self.settings, self.group_list.store_groups) for n in ['testgroup_1', 'testgroup_2', 'testgroup_3', 'testgroup_4', 'testgroup_5', 'testgroup_6', 'testgroup_7', 'testgroup_8', 'testgroup_9', 'testgroup_10', 'testgroup_11']] self.group_list.groups = groups self.group_list.store_groups() self.single_member_data = (PADDED_UTF32_STR_LEN + (2 * BOOLEAN_SETTING_LEN) + (self.settings.max_number_of_group_members * PADDED_UTF32_STR_LEN)) def tearDown(self): cleanup() def test_group_list_iterates_over_group_objects(self): for g in self.group_list: self.assertIsInstance(g, Group) def test_len_returns_number_of_groups(self): self.assertEqual(len(self.group_list), 11) def test_database_size(self): self.assertTrue(os.path.isfile(f'{DIR_USER_DATA}ut_groups')) self.assertEqual(os.path.getsize(f'{DIR_USER_DATA}ut_groups'), XSALSA20_NONCE_LEN + GROUP_DB_HEADER_LEN + self.settings.max_number_of_groups * self.single_member_data + POLY1305_TAG_LEN) self.settings.max_number_of_groups = 10 self.settings.max_number_of_group_members = 10 group_list2 = GroupList(self.master_key, self.settings, self.contact_list) self.assertEqual(len(group_list2), 11) # Check that load_groups() function increases setting values with larger db self.assertEqual(self.settings.max_number_of_groups, 20) self.assertEqual(self.settings.max_number_of_group_members, 20) # Check that removed contact from contact list updates group self.contact_list.remove_contact('Alice') group_list3 = GroupList(self.master_key, self.settings, self.contact_list) self.assertEqual(len(group_list3.get_group('testgroup_1').members), 10) group_list4 = GroupList(self.master_key, self.settings, self.contact_list) self.assertEqual(len(group_list4.get_group('testgroup_2').members), 10) def test_generate_group_db_header(self): header = self.group_list.generate_group_db_header() self.assertEqual(len(header), GROUP_DB_HEADER_LEN) self.assertIsInstance(header, bytes) def test_generate_dummy_group(self): dummy_group = self.group_list.generate_dummy_group() self.assertEqual(len(dummy_group.serialize_g()), self.single_member_data) self.assertIsInstance(dummy_group, Group) def test_add_group(self): members = [create_contact('Laura')] self.group_list.add_group('testgroup_12', False, False, members) self.group_list.add_group('testgroup_12', False, True, members) self.assertTrue(self.group_list.get_group('testgroup_12').notifications) self.assertEqual(len(self.group_list), 12) def test_remove_group(self): self.assertEqual(len(self.group_list), 11) self.assertIsNone(self.group_list.remove_group('testgroup_12')) self.assertEqual(len(self.group_list), 11) self.assertIsNone(self.group_list.remove_group('testgroup_11')) self.assertEqual(len(self.group_list), 10) def test_get_list_of_group_names(self): g_names = ['testgroup_1', 'testgroup_2', 'testgroup_3', 'testgroup_4', 'testgroup_5', 'testgroup_6', 'testgroup_7', 'testgroup_8', 'testgroup_9', 'testgroup_10', 'testgroup_11'] self.assertEqual(self.group_list.get_list_of_group_names(), g_names) def test_get_group(self): self.assertEqual(self.group_list.get_group('testgroup_3').name, 'testgroup_3') def test_get_group_members(self): members = self.group_list.get_group_members('testgroup_1') for c in members: self.assertIsInstance(c, Contact) def test_has_group(self): self.assertTrue(self.group_list.has_group('testgroup_11')) self.assertFalse(self.group_list.has_group('testgroup_12')) def test_has_groups(self): self.assertTrue(self.group_list.has_groups()) self.group_list.groups = [] self.assertFalse(self.group_list.has_groups()) def test_largest_group(self): self.assertEqual(self.group_list.largest_group(), 11) def test_print_group(self): self.group_list.get_group("testgroup_1").log_messages = True self.group_list.get_group("testgroup_2").notifications = True self.group_list.get_group("testgroup_3").members = [] self.assertPrints("""\ Group Logging Notify Members ──────────────────────────────────────────────────────────────────────────────── testgroup_1 Yes No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol testgroup_2 No Yes Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol testgroup_3 No No <Empty group> testgroup_4 No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol testgroup_5 No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol testgroup_6 No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol testgroup_7 No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol testgroup_8 No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol testgroup_9 No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol testgroup_10 No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol testgroup_11 No No Alice, Bob, Charlie, David, Eric, Fido, Guido, Heidi, Ivan, Joana, Karol """, self.group_list.print_groups)
def main() -> None: """Derive master key, decrypt databases and initialize processes.""" os.chdir(sys.path[0]) check_kernel_version() check_kernel_entropy() operation, local_test, dd_sockets = process_arguments() clear_screen() c_print(TFC, head=1, tail=1) master_key = MasterKey(operation, local_test) settings = Settings(master_key, operation, local_test, dd_sockets) contact_list = ContactList(master_key, settings) key_list = KeyList(master_key, settings) group_list = GroupList(master_key, settings, contact_list) gateway = Gateway(settings) if settings.software_operation == TX: queues = { MESSAGE_PACKET_QUEUE: Queue(), FILE_PACKET_QUEUE: Queue(), COMMAND_PACKET_QUEUE: Queue(), NH_PACKET_QUEUE: Queue(), LOG_PACKET_QUEUE: Queue(), EXIT_QUEUE: Queue(), NOISE_PACKET_QUEUE: Queue(), NOISE_COMMAND_QUEUE: Queue(), KEY_MANAGEMENT_QUEUE: Queue(), WINDOW_SELECT_QUEUE: Queue() } process_list = [ Process(target=input_loop, args=(queues, settings, gateway, contact_list, group_list, master_key, sys.stdin.fileno())), Process(target=sender_loop, args=(queues, settings, gateway, key_list)), Process(target=log_writer_loop, args=(queues, )) ] if settings.session_traffic_masking: process_list.extend([ Process(target=noise_loop, args=(P_N_HEADER, queues[NOISE_PACKET_QUEUE], contact_list)), Process(target=noise_loop, args=(C_N_HEADER, queues[NOISE_COMMAND_QUEUE])) ]) else: queues = { LOCAL_KEY_PACKET_HEADER: Queue(), PUBLIC_KEY_PACKET_HEADER: Queue(), MESSAGE_PACKET_HEADER: Queue(), COMMAND_PACKET_HEADER: Queue(), IMPORTED_FILE_HEADER: Queue(), EXIT_QUEUE: Queue(), GATEWAY_QUEUE: Queue() } process_list = [ Process(target=gateway_loop, args=(queues, gateway)), Process(target=receiver_loop, args=(queues, settings)), Process(target=output_loop, args=(queues, settings, contact_list, key_list, group_list, master_key, sys.stdin.fileno())) ] for p in process_list: p.start() while True: with ignored(EOFError, KeyboardInterrupt): time.sleep(0.1) if not all([p.is_alive() for p in process_list]): for p in process_list: p.terminate() sys.exit(1) if not queues[EXIT_QUEUE].empty(): command = queues[EXIT_QUEUE].get() for p in process_list: p.terminate() if command == WIPE: subprocess.Popen( f"find {DIR_USER_DATA} -name '{operation}*' -type f -exec shred -n 3 -z -u {{}} \;", shell=True).wait() os.system('poweroff') else: sys.exit(0)