def setUp(self): self.unittest_dir = cd_unittest() self.file_name = f"{DIR_USER_DATA}{TX}_settings" self.master_key = MasterKey() self.settings = Settings(self.master_key, operation=TX, local_test=False) self.contact_list = ContactList(nicks=[f'contact_{n}' for n in range(18)]) self.group_list = GroupList(groups=[f'group_{n}' for n in range(18)]) self.group_list.groups[0] = create_group('group_0', [f'contact_{n}' for n in range(18)]) self.args = self.contact_list, self.group_list
def test_load_of_modified_database_raises_critical_error(self) -> None: # Store settings to database self.settings.store_settings() # Test reading from database works normally self.assertIsInstance(Settings(self.master_key, operation=TX, local_test=False), Settings) # Test loading of the tampered database raises CriticalError tamper_file(self.file_name, tamper_size=1) with self.assertRaises(SystemExit): Settings(self.master_key, operation=TX, local_test=False)
def test_store_and_load_rx_settings(self) -> None: # Setup self.settings = Settings(self.master_key, operation=RX, local_test=False) # Test store self.assertFalse(self.settings.disable_gui_dialog) self.settings.disable_gui_dialog = True self.settings.store_settings() self.assertEqual(os.path.getsize(self.file_name), SETTING_LENGTH) # Test load settings2 = Settings(self.master_key, RX, False) self.assertTrue(settings2.disable_gui_dialog)
def setUp(self): self.o_input = builtins.input builtins.input = lambda _: 'yes' self.masterkey = MasterKey() self.settings = Settings(self.masterkey, operation='ut', local_test=False, dd_sockets=False) self.contact_list = ContactList( nicks=['contact_{}'.format(n) for n in range(18)]) self.group_list = GroupList( groups=['group_{}'.format(n) for n in range(18)]) self.group_list.groups[0] = create_group( 'group_0', ['contact_{}'.format(n) for n in range(18)])
def test_store_and_load_tx_settings(self) -> None: # Test store self.assertFalse(self.settings.disable_gui_dialog) self.settings.disable_gui_dialog = True self.settings.store_settings() self.assertEqual(os.path.getsize(self.file_name), SETTING_LENGTH) # Test load settings2 = Settings(self.master_key, TX, False) self.assertTrue(settings2.disable_gui_dialog)
def test_store_and_load_settings(self): # Test store self.assertFalse(self.settings.disable_gui_dialog) self.settings.disable_gui_dialog = True self.settings.store_settings() self.assertEqual(os.path.getsize(f"{DIR_USER_DATA}ut_settings"), SETTING_LENGTH) # Test load settings2 = Settings(self.masterkey, 'ut', False, False) self.assertTrue(settings2.disable_gui_dialog)
class TestSettings(TFCTestCase): def setUp(self): self.o_input = builtins.input builtins.input = lambda _: 'yes' self.masterkey = MasterKey() self.settings = Settings(self.masterkey, operation='ut', local_test=False, dd_sockets=False) self.contact_list = ContactList( nicks=['contact_{}'.format(n) for n in range(18)]) self.group_list = GroupList( groups=['group_{}'.format(n) for n in range(18)]) self.group_list.groups[0] = create_group( 'group_0', ['contact_{}'.format(n) for n in range(18)]) def tearDown(self): cleanup() builtins.input = self.o_input def test_invalid_type_raises_critical_error_on_store(self): self.settings.serial_error_correction = b'bytestring' with self.assertRaises(SystemExit): self.settings.store_settings() def test_invalid_type_raises_critical_error_on_load(self): with self.assertRaises(SystemExit): self.settings.nh_bypass_messages = b'bytestring' self.settings.load_settings() def test_store_and_load_settings(self): # Test store self.assertFalse(self.settings.disable_gui_dialog) self.settings.disable_gui_dialog = True self.settings.store_settings() self.assertEqual(os.path.getsize(f"{DIR_USER_DATA}ut_settings"), SETTING_LENGTH) # Test load settings2 = Settings(self.masterkey, 'ut', False, False) self.assertTrue(settings2.disable_gui_dialog) def test_invalid_type_raises_critical_error_when_changing_settings(self): self.settings.traffic_masking = b'bytestring' with self.assertRaises(SystemExit): self.assertIsNone( self.settings.change_setting('traffic_masking', 'True', self.contact_list, self.group_list)) def test_change_settings(self): self.assertFR("Error: Invalid value 'Falsee'", self.settings.change_setting, 'disable_gui_dialog', 'Falsee', self.contact_list, self.group_list) self.assertFR("Error: Invalid value '1.1'", self.settings.change_setting, 'max_number_of_group_members', '1.1', self.contact_list, self.group_list) self.assertFR("Error: Invalid value '-1.1'", self.settings.change_setting, 'max_duration_of_random_delay', '-1.1', self.contact_list, self.group_list) self.assertFR("Error: Invalid value '18446744073709551616'", self.settings.change_setting, 'serial_error_correction', str(2**64), self.contact_list, self.group_list) self.assertFR("Error: Invalid value 'True'", self.settings.change_setting, 'traffic_masking_static_delay', 'True', self.contact_list, self.group_list) self.assertIsNone( self.settings.change_setting('serial_error_correction', '10', self.contact_list, self.group_list)) self.assertIsNone( self.settings.change_setting('rxm_usb_serial_adapter', 'True', self.contact_list, self.group_list)) self.assertIsNone( self.settings.change_setting('traffic_masking', 'True', self.contact_list, self.group_list)) def test_validate_key_value_pair(self): self.assertFR( "Error: Database padding settings must be divisible by 10.", self.settings.validate_key_value_pair, 'max_number_of_group_members', 0, self.contact_list, self.group_list) self.assertFR( "Error: Database padding settings must be divisible by 10.", self.settings.validate_key_value_pair, 'max_number_of_group_members', 18, self.contact_list, self.group_list) self.assertFR( "Error: Database padding settings must be divisible by 10.", self.settings.validate_key_value_pair, 'max_number_of_groups', 18, self.contact_list, self.group_list) self.assertFR( "Error: Database padding settings must be divisible by 10.", self.settings.validate_key_value_pair, 'max_number_of_contacts', 18, self.contact_list, self.group_list) self.assertFR("Error: Can't set max number of members lower than 20.", self.settings.validate_key_value_pair, 'max_number_of_group_members', 10, self.contact_list, self.group_list) self.assertFR("Error: Can't set max number of groups lower than 20.", self.settings.validate_key_value_pair, 'max_number_of_groups', 10, self.contact_list, self.group_list) self.assertFR("Error: Can't set max number of contacts lower than 20.", self.settings.validate_key_value_pair, 'max_number_of_contacts', 10, self.contact_list, self.group_list) self.assertFR("Error: Specified baud rate is not supported.", self.settings.validate_key_value_pair, 'serial_baudrate', 10, self.contact_list, self.group_list) self.assertFR("Error: Invalid value for error correction ratio.", self.settings.validate_key_value_pair, 'serial_error_correction', 0, self.contact_list, self.group_list) self.assertFR("Error: Invalid value for error correction ratio.", self.settings.validate_key_value_pair, 'serial_error_correction', -1, self.contact_list, self.group_list) self.assertFR("Error: Too small value for message notify duration.", self.settings.validate_key_value_pair, 'new_message_notify_duration', 0.04, self.contact_list, self.group_list) self.assertIsNone( self.settings.validate_key_value_pair("serial_baudrate", 9600, self.contact_list, self.group_list)) def test_too_narrow_terminal_raises_fr_when_printing_settings(self): # Setup o_get_terminal_size = shutil.get_terminal_size shutil.get_terminal_size = lambda: [64, 64] # Test self.assertFR("Error: Screen width is too small.", self.settings.print_settings) # Teardown shutil.get_terminal_size = o_get_terminal_size def test_setup(self): # Setup builtins.input = lambda _: 'No' # Test self.settings.software_operation = TX self.settings.setup() self.assertFalse(self.settings.txm_usb_serial_adapter) self.settings.software_operation = RX self.settings.setup() self.assertFalse(self.settings.rxm_usb_serial_adapter) def test_print_settings(self): self.settings.max_number_of_group_members = 30 self.settings.log_messages_by_default = True self.settings.traffic_masking_static_delay = 10.2 self.assertPrints( CLEAR_ENTIRE_SCREEN + CURSOR_LEFT_UP_CORNER + """\ Setting name Current value Default value Description ──────────────────────────────────────────────────────────────────────────────── disable_gui_dialog False False True replaces Tkinter dialogs with CLI prompts max_number_of_group_members 30 20 Max members in group (TxM/RxM must have the same value) max_number_of_groups 20 20 Max number of groups (TxM/RxM must have the same value) max_number_of_contacts 20 20 Max number of contacts (TxM/RxM must have the same value) serial_baudrate 19200 19200 The speed of serial interface in bauds per second serial_error_correction 5 5 Number of byte errors serial datagrams can recover from log_messages_by_default True False Default logging setting for new contacts/groups accept_files_by_default False False Default file reception setting for new contacts show_notifications_by_default True True Default message notification setting for new contacts/groups logfile_masking False False True hides real size of logfile during traffic masking txm_usb_serial_adapter True True False uses system's integrated serial interface nh_bypass_messages True True False removes NH bypass interrupt messages confirm_sent_files True True False sends files without asking for confirmation double_space_exits False False True exits, False clears screen with double space command traffic_masking False False True enables traffic masking to hide metadata traffic_masking_static_delay 10.2 2.0 Static delay between traffic masking packets traffic_masking_random_delay 2.0 2.0 Max random delay for traffic masking timing obfuscation multi_packet_random_delay False False True adds IM server spam guard evading delay max_duration_of_random_delay 10.0 10.0 Maximum time for random spam guard evasion delay rxm_usb_serial_adapter True True False uses system's integrated serial interface new_message_notify_preview False False When True, shows preview of received message new_message_notify_duration 1.0 1.0 Number of seconds new message notification appears """, self.settings.print_settings)
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)
class TestSettings(TFCTestCase): def setUp(self) -> None: """Pre-test actions.""" self.unit_test_dir = cd_unit_test() self.file_name = f"{DIR_USER_DATA}{TX}_settings" self.master_key = MasterKey() self.settings = Settings(self.master_key, operation=TX, local_test=False) self.contact_list = ContactList(nicks=[f'contact_{n}' for n in range(18)]) self.group_list = GroupList(groups=[f'group_{n}' for n in range(18)]) self.group_list.groups[0] = create_group('group_0', [f'contact_{n}' for n in range(18)]) self.args = self.contact_list, self.group_list def tearDown(self) -> None: """Post-test actions.""" cleanup(self.unit_test_dir) def test_invalid_type_raises_critical_error_on_store(self) -> None: self.settings.tm_random_delay = b'bytestring' with self.assertRaises(SystemExit): self.settings.store_settings() def test_invalid_type_raises_critical_error_on_load(self) -> None: with self.assertRaises(SystemExit): self.settings.nc_bypass_messages = b'bytestring' self.settings.load_settings() def test_store_and_load_tx_settings(self) -> None: # Test store self.assertFalse(self.settings.disable_gui_dialog) self.settings.disable_gui_dialog = True self.settings.store_settings() self.assertEqual(os.path.getsize(self.file_name), SETTING_LENGTH) # Test load settings2 = Settings(self.master_key, TX, False) self.assertTrue(settings2.disable_gui_dialog) def test_store_and_load_rx_settings(self) -> None: # Setup self.settings = Settings(self.master_key, operation=RX, local_test=False) # Test store self.assertFalse(self.settings.disable_gui_dialog) self.settings.disable_gui_dialog = True self.settings.store_settings() self.assertEqual(os.path.getsize(self.file_name), SETTING_LENGTH) # Test load settings2 = Settings(self.master_key, RX, False) self.assertTrue(settings2.disable_gui_dialog) def test_load_of_modified_database_raises_critical_error(self) -> None: # Store settings to database self.settings.store_settings() # Test reading from database works normally self.assertIsInstance(Settings(self.master_key, operation=TX, local_test=False), Settings) # Test loading of the tampered database raises CriticalError tamper_file(self.file_name, tamper_size=1) with self.assertRaises(SystemExit): Settings(self.master_key, operation=TX, local_test=False) def test_invalid_type_raises_critical_error_when_changing_settings(self) -> None: self.settings.traffic_masking = b'bytestring' with self.assertRaises(SystemExit): self.assertIsNone(self.settings.change_setting('traffic_masking', 'True', *self.args)) def test_change_settings(self) -> None: self.assert_se("Error: Invalid setting value 'Falsee'.", self.settings.change_setting, 'disable_gui_dialog', 'Falsee', *self.args) self.assert_se("Error: Invalid setting value '1.1'.", self.settings.change_setting, 'max_number_of_group_members', '1.1', *self.args) self.assert_se("Error: Invalid setting value '18446744073709551616'.", self.settings.change_setting, 'max_number_of_contacts', str(2 ** 64), *self.args) self.assert_se("Error: Invalid setting value '-1.1'.", self.settings.change_setting, 'tm_static_delay', '-1.1', *self.args) self.assert_se("Error: Invalid setting value 'True'.", self.settings.change_setting, 'tm_static_delay', 'True', *self.args) self.assertIsNone(self.settings.change_setting('traffic_masking', 'True', *self.args)) self.assertIsNone(self.settings.change_setting('max_number_of_group_members', '100', *self.args)) @mock.patch('builtins.input', side_effect=['No', 'Yes']) def test_validate_key_value_pair(self, _: Any) -> None: self.assert_se("Error: Database padding settings must be divisible by 10.", self.settings.validate_key_value_pair, 'max_number_of_group_members', 0, *self.args) self.assert_se("Error: Database padding settings must be divisible by 10.", self.settings.validate_key_value_pair, 'max_number_of_group_members', 18, *self.args) self.assert_se("Error: Database padding settings must be divisible by 10.", self.settings.validate_key_value_pair, 'max_number_of_groups', 18, *self.args) self.assert_se("Error: Database padding settings must be divisible by 10.", self.settings.validate_key_value_pair, 'max_number_of_contacts', 18, *self.args) self.assert_se("Error: Can't set the max number of members lower than 20.", self.settings.validate_key_value_pair, 'max_number_of_group_members', 10, *self.args) self.assert_se("Error: Can't set the max number of groups lower than 20.", self.settings.validate_key_value_pair, 'max_number_of_groups', 10, *self.args) self.assert_se("Error: Can't set the max number of contacts lower than 20.", self.settings.validate_key_value_pair, 'max_number_of_contacts', 10, *self.args) self.assert_se("Error: Too small value for message notify duration.", self.settings.validate_key_value_pair, 'new_message_notify_duration', 0.04, *self.args) self.assert_se("Error: Can't set static delay lower than 0.1.", self.settings.validate_key_value_pair, 'tm_static_delay', 0.01, *self.args) self.assert_se("Error: Can't set random delay lower than 0.1.", self.settings.validate_key_value_pair, 'tm_random_delay', 0.01, *self.args) self.assert_se("Aborted traffic masking setting change.", self.settings.validate_key_value_pair, 'tm_random_delay', 0.1, *self.args) self.assertIsNone(self.settings.validate_key_value_pair("serial_baudrate", 9600, *self.args)) self.assertIsNone(self.settings.validate_key_value_pair("tm_static_delay", 1, *self.args)) @mock.patch('shutil.get_terminal_size', return_value=(64, 64)) def test_too_narrow_terminal_raises_fr_when_printing_settings(self, _: Any) -> None: # Test self.assert_se("Error: Screen width is too small.", self.settings.print_settings) def test_print_settings(self) -> None: self.settings.max_number_of_group_members = 30 self.settings.log_messages_by_default = True self.settings.tm_static_delay = 10.2 self.assert_prints(CLEAR_ENTIRE_SCREEN + CURSOR_LEFT_UP_CORNER + """\ Setting name Current value Default value Description ──────────────────────────────────────────────────────────────────────────────── disable_gui_dialog False False True replaces GUI dialogs with CLI prompts max_number_of_group_members 30 50 Maximum number of members in a group max_number_of_groups 50 50 Maximum number of groups max_number_of_contacts 50 50 Maximum number of contacts log_messages_by_default True False Default logging setting for new contacts/groups accept_files_by_default False False Default file reception setting for new contacts show_notifications_by_default True True Default message notification setting for new contacts/groups log_file_masking False False True hides real size of log file during traffic masking ask_password_for_log_access True True False disables password prompt when viewing/exp orting logs nc_bypass_messages False False False removes Networked Computer bypass interrupt messages confirm_sent_files True True False sends files without asking for confirmation double_space_exits False False True exits, False clears screen with double space command traffic_masking False False True enables traffic masking to hide metadata tm_static_delay 10.2 2.0 The static delay between traffic masking packets tm_random_delay 2.0 2.0 Max random delay for traffic masking timing obfuscation allow_contact_requests True True When False, does not show TFC contact requests new_message_notify_preview False False When True, shows a preview of the received message new_message_notify_duration 1.0 1.0 Number of seconds new message notification appears max_decompress_size 100000000 100000000 Max size Receiver accepts when decompressing file """, self.settings.print_settings)
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
def test_class(self): # Setup masterkey = MasterKey() o_input = builtins.input builtins.input = lambda x: 'yes' settings = Settings(masterkey, 'ut', False, False) contact_list = ContactList(nicks=['contact_{}'.format(n) for n in range(18)]) group_list = GroupList(groups =['group_{}'.format(n) for n in range(18)]) group_list.groups[0] = create_group('group_0', ['contact_{}'.format(n) for n in range(18)]) # Test store/load self.assertFalse(settings.disable_gui_dialog) settings.disable_gui_dialog = True settings.store_settings() self.assertTrue(os.path.isfile(f"{DIR_USER_DATA}/ut_settings")) self.assertEqual(os.path.getsize(f"{DIR_USER_DATA}/ut_settings"), 24 + 1024 + 9*8 + 12*1 + 16) settings2 = Settings(masterkey, 'ut', False, False) self.assertTrue(settings2.disable_gui_dialog) settings2.format_of_logfiles = b'invalid' with self.assertRaises(SystemExit): settings2.store_settings() with self.assertRaises(SystemExit): settings2.change_setting('format_of_logfiles', '%Y-%m-%d %H:%M:%S', contact_list, group_list) settings2.format_of_logfiles = '%Y-%m-%d %H:%M:%S' # Test change_setting self.assertFR('Invalid value Falsee.', settings2.change_setting, 'disable_gui_dialog', 'Falsee', contact_list, group_list) self.assertFR('Invalid value 1.1.', settings2.change_setting, 'm_members_in_group', '1.1', contact_list, group_list) self.assertFR('Invalid value 7378697629483820650.', settings2.change_setting, 'm_members_in_group', '7378697629483820650', contact_list, group_list) self.assertFR('Invalid value True.', settings2.change_setting, 'trickle_stat_delay', 'True', contact_list, group_list) self.assertFR("Setting must be shorter than 256 chars.", settings2.change_setting, 'format_of_logfiles', 256*'a', contact_list, group_list) self.assertIsNone(settings2.change_setting('format_of_logfiles', '%Y-%m-%d %H:%M:%S', contact_list, group_list)) self.assertIsNone(settings2.change_setting('e_correction_ratio', '10', contact_list, group_list)) self.assertIsNone(settings2.change_setting('rxm_serial_adapter', 'True', contact_list, group_list)) self.assertIsNone(settings2.change_setting('trickle_connection', 'True', contact_list, group_list)) self.assertFR("Database padding settings must be divisible by 10.", settings2.validate_key_value_pair, 'm_members_in_group', '18', contact_list, group_list) self.assertFR("Database padding settings must be divisible by 10.", settings2.validate_key_value_pair, 'm_number_of_groups', '18', contact_list, group_list) self.assertFR("Database padding settings must be divisible by 10.", settings2.validate_key_value_pair, 'm_number_of_accnts', '18', contact_list, group_list) self.assertFR("Can't set max number of members lower than 20.", settings2.validate_key_value_pair, 'm_members_in_group', '10', contact_list, group_list) self.assertFR("Can't set max number of groups lower than 20.", settings2.validate_key_value_pair, 'm_number_of_groups', '10', contact_list, group_list) self.assertFR("Can't set max number of contacts lower than 20.", settings2.validate_key_value_pair, 'm_number_of_accnts', '10', contact_list, group_list) self.assertFR("Specified baud rate is not supported.", settings2.validate_key_value_pair, 'serial_iface_speed', '10', contact_list, group_list) self.assertFR("Invalid value for error correction ratio.", settings2.validate_key_value_pair, 'e_correction_ratio', '0', contact_list, group_list) self.assertFR("Invalid value for error correction ratio.", settings2.validate_key_value_pair, 'e_correction_ratio', 'a', contact_list, group_list) self.assertFR("Invalid value for error correction ratio.", settings2.validate_key_value_pair, 'e_correction_ratio', '-1', contact_list, group_list) self.assertIsNone(settings2.print_settings()) builtins.input = o_input
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)