Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
    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)
Beispiel #4
0
    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)
Beispiel #5
0
    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)
Beispiel #6
0
    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))
Beispiel #7
0
    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()
Beispiel #8
0
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)
Beispiel #9
0
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
Beispiel #10
0
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)