Example #1
0
    def test_message_length(self):
        # Check that only 256-byte plaintext messages are ever allowed
        for l in range(1, 256):
            with self.assertRaises(SystemExit):
                send_packet(self.key_list, self.gateway, self.l_queue,
                            bytes(l), self.settings, '*****@*****.**',
                            '*****@*****.**', True)

        for l in range(257, 300):
            with self.assertRaises(SystemExit):
                send_packet(self.key_list, self.gateway, self.l_queue,
                            bytes(l), self.settings, '*****@*****.**',
                            '*****@*****.**', True)
Example #2
0
    def test_invalid_account_raises_stop_iteration(self):
        # Check that in case where internal error caused bytestring (possible key material)
        # to end up in account strings, System raises some error that prevents output of packet.
        # In this case the error comes from unsuccessful encoding of string (AttributeError)
        # or KeyList lookup error when bytes are used (StopIteration). These errors are not catched.
        with self.assertRaises(StopIteration):
            send_packet(self.key_list, self.gateway, self.l_queue,
                        bytes(ASSEMBLY_PACKET_LEN), self.settings,
                        b'*****@*****.**', '*****@*****.**', True)

        with self.assertRaises(AttributeError):
            send_packet(self.key_list, self.gateway, self.l_queue,
                        bytes(ASSEMBLY_PACKET_LEN), self.settings,
                        '*****@*****.**', b'*****@*****.**', True)
Example #3
0
    def test_invalid_harac_raises_raises_struct_error(self):
        # Check that in case where internal error caused bytestring (possible key material)
        # to end up in hash ratchet value, system raises some error that prevents output of packet.
        # In this case the error comes from unsuccessful encoding of hash ratchet counter.
        for l in range(1, 33):
            key_list = KeyList()
            key_list.keysets = [
                create_keyset(tx_key=KEY_LENGTH * b'\x02', tx_harac=l * b'k')
            ]

            with self.assertRaises(struct.error):
                send_packet(key_list, self.gateway, self.l_queue,
                            bytes(ASSEMBLY_PACKET_LEN), self.settings,
                            '*****@*****.**', '*****@*****.**', True)
Example #4
0
    def test_message_length(self):
        # Setup
        key_list = KeyList()
        settings = Settings()
        gateway = Gateway()
        l_queue = Queue()

        # Check that only 256-byte plaintext messages are ever allowed
        for l in range(1, 256):
            with self.assertRaises(SystemExit):
                send_packet(bytes(l), key_list, settings, gateway, l_queue,
                            '*****@*****.**', '*****@*****.**', True)

        for l in range(257, 300):
            with self.assertRaises(SystemExit):
                send_packet(bytes(l), key_list, settings, gateway, l_queue,
                            '*****@*****.**', '*****@*****.**', True)
Example #5
0
    def test_invalid_account_crashes(self):
        # Setup
        settings = Settings()
        gateway = Gateway()
        l_queue = Queue()
        key_list = KeyList()
        key_list.keysets = [create_keyset('Alice')]

        # Check that in case where internal error caused bytestring (possible key material)
        # to end up in account strings, System raises some error that prevents output of packet.
        # In this case the error comes from unsuccessful encoding of string (AttributeError)
        # or KeyList lookup error when bytes are used (StopIteration). These errors are not catched.
        with self.assertRaises(StopIteration):
            send_packet(bytes(256), key_list, settings, gateway, l_queue,
                        b'*****@*****.**', '*****@*****.**', True)
        with self.assertRaises(AttributeError):
            send_packet(bytes(256), key_list, settings, gateway, l_queue,
                        '*****@*****.**', b'*****@*****.**', True)
Example #6
0
    def test_invalid_harac_crashes(self):
        # Setup
        settings = Settings()
        gateway = Gateway()
        l_queue = Queue()

        # Check that in case where internal error caused bytestring (possible key material)
        # to end up in hash ratchet value, system raises some error that prevents output of packet.
        # In this case the error comes from unsuccessful encoding of hash ratchet counter.
        for l in range(1, 32):
            key_list = KeyList()
            key_list.keysets = [
                create_keyset(tx_hek=32 * b'\x01',
                              tx_key=32 * b'\x02',
                              tx_harac=l * b'k')
            ]

            with self.assertRaises(struct.error):
                send_packet(bytes(256), key_list, settings, gateway, l_queue,
                            '*****@*****.**', '*****@*****.**', True)
Example #7
0
    def test_valid_message_packet(self):
        # Setup
        settings = Settings(long_packet_rand_d=True)
        gateway = Gateway()
        l_queue = Queue()
        key_list = KeyList(master_key=bytes(32))
        key_list.keysets = [
            create_keyset(tx_hek=32 * b'\x01', tx_key=32 * b'\x02', tx_harac=8)
        ]

        # Test
        self.assertIsNone(
            send_packet(bytes(256), key_list, settings, gateway, l_queue,
                        '*****@*****.**', '*****@*****.**', True))
        self.assertEqual(len(gateway.packets), 1)
        self.assertEqual(len(gateway.packets[0]), 396)

        time.sleep(0.2)
        self.assertFalse(l_queue.empty())
Example #8
0
    def test_valid_command_packet(self):
        """Test that commands are output as they should.

        Since command packets have no trailer, and since only user's
        RxM has local decryption key, encryption with any key recipient
        is not already in possession of does not compromise plaintext.
        """
        # Setup
        key_list = KeyList(master_key=bytes(KEY_LENGTH))
        key_list.keysets = [create_keyset(LOCAL_ID)]

        # Test
        self.assertIsNone(
            send_packet(key_list, self.gateway, self.l_queue,
                        bytes(ASSEMBLY_PACKET_LEN), self.settings))
        time.sleep(0.1)

        self.assertEqual(len(self.gateway.packets), 1)
        self.assertEqual(len(self.gateway.packets[0]), 365)
        self.assertEqual(self.l_queue.qsize(), 1)
Example #9
0
    def test_valid_message_packet(self):
        # Setup
        settings = Settings(multi_packet_random_delay=True)
        gateway = Gateway()
        key_list = KeyList(master_key=bytes(KEY_LENGTH))
        key_list.keysets = [
            create_keyset(tx_key=KEY_LENGTH * b'\x02', tx_harac=8)
        ]

        # Test
        self.assertIsNone(
            send_packet(key_list, gateway, self.l_queue,
                        bytes(ASSEMBLY_PACKET_LEN), settings,
                        '*****@*****.**', '*****@*****.**', True))

        self.assertEqual(len(gateway.packets), 1)
        self.assertEqual(len(gateway.packets[0]), 396)

        time.sleep(0.1)
        self.assertFalse(self.l_queue.empty())
Example #10
0
    def test_valid_command_packet(self):
        """\
        Test that commands are output as they should
        Since command packets have no trailer, and since only user's RxM has local decryption key,
        encryption with any key recipient is not already in possession of does not compromise plaintext.
        """
        # Setup
        settings = Settings()
        gateway = Gateway()
        l_queue = Queue()
        key_list = KeyList(master_key=bytes(32))
        key_list.keysets = [create_keyset('local')]

        # Test
        self.assertIsNone(
            send_packet(bytes(256), key_list, settings, gateway, l_queue))
        self.assertEqual(len(gateway.packets), 1)
        self.assertEqual(len(gateway.packets[0]), 365)

        time.sleep(0.2)
        self.assertTrue(l_queue.empty())
Example #11
0
def sender_loop(settings: 'Settings', queues: Dict[bytes, 'Queue'],
                gateway: 'Gateway', key_list: 'KeyList') -> None:
    """Load assembly packets from queues based on their priority, encrypt and output them.

    Sender loop handles a set of queues. As Python's multiprocessing lacks priority queues,
    several queues are prioritized based on their status. In both trickle and non-trickle
    mode, file are only transmitted when no messages are being output. This is because file
    transmission is usually very slow and user might need to send messages in the meantime.
    In normal (non-trickle) mode commands take highest priority as they are not output
    all the time. In trickle mode commands are output between each output message packet.
    This allows commands to take effect as soon as possible but slows down message/file delivery
    by half. In trickle mode each contact in window is cycled in order. Making changes to
    recipient list during use is prevented to protect user from accidentally revealing use
    of TFC. In trickle mode, if no packets are available in either m_queue or f_queue,
    a noise assembly packet is loaded from np_queue. If no command packet is available in
    c_queue, a noise command packet is loaded from nc_queue. TFC does it's best to hide the
    loading times and encryption duration by using constant time context manager and constant
    time queue status lookup, as well as constant time XSalsa20 cipher.
    """
    m_queue = queues[MESSAGE_PACKET_QUEUE]
    f_queue = queues[FILE_PACKET_QUEUE]
    c_queue = queues[COMMAND_PACKET_QUEUE]
    l_queue = queues[LOG_PACKET_QUEUE]
    km_queue = queues[KEY_MANAGEMENT_QUEUE]
    np_queue = queues[NOISE_PACKET_QUEUE]
    nc_queue = queues[NOISE_COMMAND_QUEUE]
    ws_queue = queues[WINDOW_SELECT_QUEUE]

    m_buffer = []  # type: List[Tuple[bytes, Settings, str, str, bool, Window]]
    f_buffer = []  # type: List[Tuple[bytes, Settings, str, str, bool, Window]]

    if settings.session_trickle:

        while ws_queue.empty():
            time.sleep(0.01)

        window = ws_queue.get()

        while True:
            try:
                with ConstantTime(settings, length=TRICKLE_QUEUE_CHECK_DELAY):
                    queue = [[m_queue, m_queue],
                             [f_queue,
                              np_queue]][m_queue.empty()][f_queue.empty()]
                    packet, log_dict = queue.get()

                for c in window:

                    with ConstantTime(settings, d_type='trickle'):
                        send_packet(packet, key_list, settings, gateway,
                                    l_queue, c.rx_account, c.tx_account,
                                    log_dict[c.rx_account])

                    with ConstantTime(settings, d_type='trickle'):
                        queue = [c_queue, nc_queue][c_queue.empty()]
                        command = queue.get()
                        send_packet(command, key_list, settings, gateway,
                                    l_queue)

            except (EOFError, KeyboardInterrupt):
                pass

    else:
        while True:
            try:
                time.sleep(0.001)

                # Keylist database management packets have highest priority.
                if not km_queue.empty():
                    command, *params = km_queue.get()
                    key_list.manage(command, *params)
                    continue

                # packets from c_queue come only from local contact. Until keys for local contact
                # have been added, no command is loaded. Commands have second highest priority.
                if not c_queue.empty():
                    if key_list.has_local_key():
                        command, settings = c_queue.get()
                        send_packet(command, key_list, settings, gateway,
                                    l_queue)
                        continue

                # Iterate through buffer list that contains tuples of transmission information
                # loaded from m_queue in the order they were placed into the buffer. As soon as
                # keys are available, send packet. Restart the loop to prioritize keylist
                # management and command packets before going through the buffer list again.
                for i, params in enumerate(m_buffer):
                    packet, settings, rx_account, tx_account, logging, window = params
                    if key_list.has_keyset(rx_account):
                        m_buffer.pop(i)
                        send_packet(packet, key_list, settings, gateway,
                                    l_queue, rx_account, tx_account, logging)
                        continue

                # Any new messages take priority only after the ones in buffer are sent.
                # If key is not on list, place the message packet into the buffer.
                if not m_queue.empty():
                    packet, settings, rx_account, tx_account, logging, window = m_queue.get(
                    )
                    if key_list.has_keyset(rx_account):
                        send_packet(packet, key_list, settings, gateway,
                                    l_queue, rx_account, tx_account, logging)
                    else:
                        m_buffer.append((packet, settings, rx_account,
                                         tx_account, logging, window))
                    continue

                # When no more messages can be processed, check if the
                # file buffer has packets that can be sent to contacts.
                for i, params in enumerate(f_buffer):
                    packet, settings, rx_account, tx_account, logging, window = params
                    if key_list.has_keyset(rx_account):
                        f_buffer.pop(i)
                        send_packet(packet, key_list, settings, gateway,
                                    l_queue, rx_account, tx_account, logging)
                        continue

                # If file buffer is empty, check if new file packets are available. If there are and
                # contact has key, send file packet, otherwise place it into the file packet buffer.
                if not f_queue.empty():
                    packet, settings, rx_account, tx_account, logging, window = f_queue.get(
                    )
                    if key_list.has_keyset(rx_account):
                        send_packet(packet, key_list, settings, gateway,
                                    l_queue, rx_account, tx_account, logging)
                    else:
                        f_buffer.append((packet, settings, rx_account,
                                         tx_account, logging, window))

            except (EOFError, KeyboardInterrupt):
                pass
Example #12
0
def sender_loop(queues: Dict[bytes, 'Queue'],
                settings: 'Settings',
                gateway: 'Gateway',
                key_list: 'KeyList',
                unittest: bool = False) -> None:
    """Output packets from queues based on queue priority.

    Sender loop loads assembly packets from a set of queues. As
    Python's multiprocessing lacks priority queues, several queues are
    prioritized based on their status. Whether or not traffic masking
    is enabled, files are only transmitted when no messages are being
    output. This is because file transmission is usually very slow and
    user might need to send messages in the meantime. When traffic
    masking is disabled, commands take highest priority as they are not
    output all the time. When traffic masking is enabled, commands are
    output between each output message packet. This allows commands to
    take effect as soon as possible but slows down message/file
    delivery by half. Each contact in window is cycled in order.

    Making changes to recipient list during use is prevented to protect
    user from accidentally revealing use of TFC. When traffic masking
    is enabled, if no packets are available in either m_queue or f_queue,
    a noise assembly packet is loaded from np_queue. If no command packet
    is available in c_queue, a noise command packet is loaded from
    nc_queue. TFC does it's best to hide the loading times and encryption
    duration by using constant time context manager with CSPRNG spawned
    jitter, constant time queue status lookup, and constant time XSalsa20
    cipher. However, since TFC is written with in a high-level language,
    it is impossible to guarantee TxM never reveals it's user-operation
    schedule to NH.
    """
    m_queue = queues[MESSAGE_PACKET_QUEUE]
    f_queue = queues[FILE_PACKET_QUEUE]
    c_queue = queues[COMMAND_PACKET_QUEUE]
    n_queue = queues[NH_PACKET_QUEUE]
    l_queue = queues[LOG_PACKET_QUEUE]
    km_queue = queues[KEY_MANAGEMENT_QUEUE]
    np_queue = queues[NOISE_PACKET_QUEUE]
    nc_queue = queues[NOISE_COMMAND_QUEUE]
    ws_queue = queues[WINDOW_SELECT_QUEUE]

    m_buffer = dict(
    )  # type: Dict[str, List[Tuple[bytes, Settings, str, str, bool]]]
    f_buffer = dict(
    )  # type: Dict[str, List[Tuple[bytes, Settings, str, str, bool]]]

    if settings.session_traffic_masking:

        while ws_queue.qsize() == 0:
            time.sleep(0.01)

        window, log_messages = ws_queue.get()

        while True:
            with ignored(EOFError, KeyboardInterrupt):
                with ConstantTime(settings,
                                  length=TRAFFIC_MASKING_QUEUE_CHECK_DELAY):
                    queue = [[m_queue, m_queue], [f_queue, np_queue]
                             ][m_queue.qsize() == 0][f_queue.qsize() == 0]

                    packet, lm, log_as_ph = queue.get()

                    if lm is not None:  # Ignores None sent by noise_packet_loop that does not alter log setting
                        log_messages = lm

                for c in window:

                    with ConstantTime(settings, d_type=TRAFFIC_MASKING):
                        send_packet(key_list, gateway, l_queue, packet,
                                    settings, c.rx_account, c.tx_account,
                                    log_messages, log_as_ph)

                    with ConstantTime(settings, d_type=TRAFFIC_MASKING):
                        queue = [c_queue, nc_queue][c_queue.qsize() == 0]
                        command, lm = queue.get()

                        if lm is not None:  # Log setting is only updated with 'logging' command
                            log_messages = lm

                        send_packet(key_list, gateway, l_queue, command,
                                    settings)

                        if n_queue.qsize() != 0:
                            packet, delay, settings = n_queue.get()
                            transmit(packet, settings, gateway, delay)
                            if packet[1:] == UNENCRYPTED_EXIT_COMMAND:
                                queues[EXIT_QUEUE].put(EXIT)
                            elif packet[1:] == UNENCRYPTED_WIPE_COMMAND:
                                queues[EXIT_QUEUE].put(WIPE)

                if unittest:
                    break

    else:
        while True:
            try:
                if km_queue.qsize() != 0:
                    key_list.manage(*km_queue.get())
                    continue

                # Commands to RxM
                if c_queue.qsize() != 0:
                    if key_list.has_local_key():
                        send_packet(key_list, gateway, l_queue, *c_queue.get())
                        continue

                # Commands/exported files to NH
                if n_queue.qsize() != 0:
                    packet, delay, settings = n_queue.get()
                    transmit(packet, settings, gateway, delay)

                    if packet[1:] == UNENCRYPTED_EXIT_COMMAND:
                        queues[EXIT_QUEUE].put(EXIT)
                    elif packet[1:] == UNENCRYPTED_WIPE_COMMAND:
                        queues[EXIT_QUEUE].put(WIPE)
                    continue

                # Buffered messages
                for rx_account in m_buffer:
                    if key_list.has_keyset(
                            rx_account) and m_buffer[rx_account]:
                        send_packet(
                            key_list, gateway, l_queue,
                            *m_buffer[rx_account].pop(0)[:-1]
                        )  # Strip window UID as it's only used to cancel packets
                        continue

                # New messages
                if m_queue.qsize() != 0:
                    q_data = m_queue.get()
                    rx_account = q_data[2]

                    if key_list.has_keyset(rx_account):
                        send_packet(key_list, gateway, l_queue, *q_data[:-1])
                    else:
                        m_buffer.setdefault(rx_account, []).append(q_data)
                    continue

                # Buffered files
                for rx_account in m_buffer:
                    if key_list.has_keyset(
                            rx_account) and f_buffer[rx_account]:
                        send_packet(key_list, gateway, l_queue,
                                    *f_buffer[rx_account].pop(0)[:-1])
                        continue

                # New files
                if f_queue.qsize() != 0:
                    q_data = f_queue.get()
                    rx_account = q_data[2]

                    if key_list.has_keyset(rx_account):
                        send_packet(key_list, gateway, l_queue, *q_data[:-1])
                    else:
                        f_buffer.setdefault(rx_account, []).append(q_data)

                if unittest and queues[UNITTEST_QUEUE].qsize() != 0:
                    break

                time.sleep(0.01)

            except (EOFError, KeyboardInterrupt):
                pass