예제 #1
0
    def test_message_length(self):
        # Check that only 256-byte plaintext messages are ever allowed
        pub_key = nick_to_pub_key("Alice")
        for l in range(1, 256):
            with self.assertRaises(SystemExit):
                send_packet(self.key_list, self.gateway, self.l_queue, bytes(l), pub_key, True)

        for l in range(257, 300):
            with self.assertRaises(SystemExit):
                send_packet(self.key_list, self.gateway, self.l_queue, bytes(l), pub_key, True)
예제 #2
0
def process_command(queues: 'QueueDict', key_list: 'KeyList',
                    gateway: 'Gateway') -> None:
    """Process command."""
    c_queue = queues[COMMAND_PACKET_QUEUE]
    log_queue = queues[LOG_PACKET_QUEUE]

    if c_queue.qsize():
        if key_list.has_local_keyset():
            send_packet(key_list, gateway, log_queue, c_queue.get())
        raise SoftError("Command processing complete.", output=False)
예제 #3
0
def process_buffered_messages(m_buffer: 'MessageBuffer', queues: 'QueueDict',
                              key_list: 'KeyList', gateway: 'Gateway') -> None:
    """Process messages cached in `m_buffer`."""
    log_queue = queues[LOG_PACKET_QUEUE]

    for onion_pub_key in m_buffer:
        if key_list.has_keyset(onion_pub_key) and m_buffer[onion_pub_key]:
            send_packet(key_list, gateway, log_queue,
                        *m_buffer[onion_pub_key].pop(0)[:-1])
            raise SoftError("Buffered message processing complete.",
                            output=False)
예제 #4
0
    def test_invalid_harac_raises_raises_struct_error(self):
        # Check that in the case where an internal error caused bytestring (possible key material) to end up in hash
        # ratchet value, the system raises some error that prevents the output of packet. In this case the, error comes
        # from the unsuccessful encoding of hash ratchet counter.
        for l in range(1, 33):
            key_list         = KeyList()
            key_list.keysets = [create_keyset('Alice',
                                              tx_key=SYMMETRIC_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_LENGTH), nick_to_pub_key("Alice"), True)
예제 #5
0
def process_new_message(m_buffer: 'MessageBuffer', queues: 'QueueDict',
                        key_list: 'KeyList', gateway: 'Gateway') -> None:
    """Process new message in message queue."""
    m_queue = queues[MESSAGE_PACKET_QUEUE]
    log_queue = queues[LOG_PACKET_QUEUE]

    if m_queue.qsize():
        queue_data = m_queue.get(
        )  # type: Tuple[bytes, bytes, bool, bool, bytes]
        onion_pub_key = queue_data[1]

        if key_list.has_keyset(onion_pub_key):
            send_packet(key_list, gateway, log_queue, *queue_data[:-1])
        else:
            m_buffer.setdefault(onion_pub_key, []).append(queue_data)

        raise SoftError("New message processing complete.", output=False)
예제 #6
0
    def test_valid_message_packet(self):
        # Setup
        gateway          = Gateway(serial_error_correction=5)
        key_list         = KeyList(master_key=bytes(SYMMETRIC_KEY_LENGTH))
        key_list.keysets = [create_keyset('Alice',
                                          tx_key=SYMMETRIC_KEY_LENGTH * b'\x02',
                                          tx_harac=8)]

        # Test
        self.assertIsNone(send_packet(key_list, gateway, self.l_queue,
                                      bytes(ASSEMBLY_PACKET_LENGTH), nick_to_pub_key("Alice"), True))
        self.assertEqual(len(gateway.packets), 1)
        time.sleep(0.01)
        self.assertFalse(self.l_queue.empty())
예제 #7
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
        Receiver Program 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(SYMMETRIC_KEY_LENGTH))
        key_list.keysets = [create_keyset(LOCAL_ID)]

        # Test
        self.assertIsNone(send_packet(key_list, self.gateway, self.l_queue,
                                      bytes(ASSEMBLY_PACKET_LENGTH)))
        self.assertEqual(len(self.gateway.packets), 1)
        self.assertEqual(len(self.gateway.packets[0]), 345)
        self.assertEqual(self.l_queue.qsize(), 1)
예제 #8
0
def traffic_masking_loop(
    queues: 'QueueDict',
    settings: 'Settings',
    gateway: 'Gateway',
    key_list: 'KeyList',
) -> 'Settings':
    """Run Transmitter Program in traffic masking mode.

    The traffic masking loop loads assembly packets from a set of queues.
    As Python's multiprocessing lacks priority queues, several queues are
    prioritized based on their status.

    Files are only transmitted when messages are not being output: This
    is because file transmission is usually very slow and the user might
    need to send messages in the meantime. Command datagrams are output
    from Source Computer between each message datagram. The frequency in
    output allows commands to take effect as soon as possible but this
    unfortunately slows down message/file delivery by half. Each contact
    in the window is cycled in order.

    When this loop is active, making changes to the recipient list is
    prevented to protect the user from accidentally revealing the use of
    TFC.

    The traffic is masked the following way: If both m_queue and f_queue
    are empty, 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. Both noise queues are filled by independent
    processes that ensure both noise queues always have packets to
    output.

    TFC does its best to hide the assembly packet loading times and
    encryption duration by using constant time context manager with
    CSPRNG spawned jitter, constant time queue status lookup and constant
    time XChaCha20 cipher. However, since TFC is written in a high-level
    language, it is impossible to guarantee Source Computer never
    reveals to Networked Computer when the user operates the Source
    Computer.
    """
    ws_queue = queues[WINDOW_SELECT_QUEUE]
    m_queue = queues[TM_MESSAGE_PACKET_QUEUE]
    f_queue = queues[TM_FILE_PACKET_QUEUE]
    c_queue = queues[TM_COMMAND_PACKET_QUEUE]
    np_queue = queues[TM_NOISE_PACKET_QUEUE]
    nc_queue = queues[TM_NOISE_COMMAND_QUEUE]
    log_queue = queues[LOG_PACKET_QUEUE]
    sm_queue = queues[SENDER_MODE_QUEUE]

    while True:
        with ignored(EOFError, KeyboardInterrupt):
            while ws_queue.qsize() == 0:
                time.sleep(0.01)
            window_contacts = ws_queue.get()

            # Window selection command to Receiver Program.
            while c_queue.qsize() == 0:
                time.sleep(0.01)
            send_packet(key_list, gateway, log_queue, c_queue.get())
            break

    while True:
        with ignored(EOFError, KeyboardInterrupt):
            # Load message/file assembly packet.
            with HideRunTime(settings,
                             duration=TRAFFIC_MASKING_QUEUE_CHECK_DELAY):

                # Choosing element from list is constant time.
                #
                #         First queue we evaluate: if m_queue has data                  Second to evaluate. If m_queue
                #         in it, False is evaluated as 0, and we load                   has no data but f_queue has, the
                #         the first nested list. At that point we load                  False is evaluated as 0 meaning
                #         from m_queue regardless of f_queue state.                     f_queue (True as 1 and np_queue)
                #                                                 |                     |
                #                                                 v                     v
                queue = [[m_queue, m_queue], [f_queue, np_queue]
                         ][m_queue.qsize() == 0][f_queue.qsize() == 0]

                # Regardless of queue, each .get() returns a tuple with identical
                # amount of data: 256 bytes long bytestring and two booleans.
                assembly_packet, log_messages, log_as_ph = queue.get(
                )  # type: bytes, bool, bool

            for c in window_contacts:
                # Message/file assembly packet to window contact.
                with HideRunTime(settings, delay_type=TRAFFIC_MASKING):
                    send_packet(key_list, gateway, log_queue, assembly_packet,
                                c.onion_pub_key, log_messages)

                # Send a command between each assembly packet for each contact.
                with HideRunTime(settings, delay_type=TRAFFIC_MASKING):

                    # Choosing element from list is constant time.
                    queue = [c_queue, nc_queue][c_queue.qsize() == 0]

                    # Each loaded command and noise command is a 256 long bytestring.
                    command = queue.get()  # type: bytes

                    send_packet(key_list, gateway, log_queue, command)

                    exit_packet_check(queues, gateway)

            # If traffic masking has been disabled, wait until queued messages are sent before returning.
            if sm_queue.qsize() != 0 and all(
                    q.qsize() == 0 for q in (m_queue, f_queue, c_queue)):
                settings = sm_queue.get()
                return settings
예제 #9
0
파일: sender_loop.py 프로젝트: todun/tfc
def standard_sender_loop(queues:   'QueueDict',
                         gateway:  'Gateway',
                         key_list: 'KeyList',
                         m_buffer: Optional['Message_buffer'] = None
                         ) -> Tuple['Settings', 'Message_buffer']:
    """Run Transmitter program in standard send mode.

    The standard 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:

    KEY_MANAGEMENT_QUEUE has the highest priority. This is to ensure the
    no queued message/command is encrypted with expired keyset.

    COMMAND_PACKET_QUEUE has the second highest priority, to ensure
    commands are issued swiftly to Receiver program. Some commands like
    screen clearing might need to be issued quickly.

    RELAY_PACKET_QUEUE has third highest priority. These are still
    commands but since Relay Program does not handle sensitive data,
    issuing commands to that devices does not take priority.

    Buffered messages have fourth highest priority. This ensures that if
    for whatever reason the keyset is removed, buffered messages do not
    get lost. Packets are loaded from the buffer in FIFO basis ensuring
    packets arrive to the recipient in order.

    MESSAGE_PACKET_QUEUE has fifth highest priority. Any buffered
    messages need to arrive earlier, thus new messages must be
    prioritized after the buffered ones.

    SENDER_MODE_QUEUE has sixth highest priority. This prevents outgoing
    packets from being left in the queues used by this loop. This queue
    returns up-to-date settings object for `sender_loop` parent loop,
    that in turn uses it to start `traffic_masking_loop`.

    Along with settings, this function returns the m_buffer status so that
    assembly packets that could not have been sent due to missing key
    can be output later, if the user resumes to standard_sender_loop and
    adds new keys for the contact.
    """
    km_queue  = queues[KEY_MANAGEMENT_QUEUE]
    c_queue   = queues[COMMAND_PACKET_QUEUE]
    rp_queue  = queues[RELAY_PACKET_QUEUE]
    m_queue   = queues[MESSAGE_PACKET_QUEUE]
    sm_queue  = queues[SENDER_MODE_QUEUE]
    log_queue = queues[LOG_PACKET_QUEUE]

    if m_buffer is None:
        m_buffer = dict()

    while True:
        with ignored(EOFError, KeyboardInterrupt):
            if km_queue.qsize() != 0:
                key_list.manage(*km_queue.get())
                continue

            # Commands to Receiver
            if c_queue.qsize() != 0:
                if key_list.has_local_keyset():
                    send_packet(key_list, gateway, log_queue, c_queue.get())
                continue

            # Commands/files to Networked Computer
            if rp_queue.qsize() != 0:
                packet = rp_queue.get()
                gateway.write(packet)

                command = packet[DATAGRAM_HEADER_LENGTH:]
                if command in [UNENCRYPTED_EXIT_COMMAND, UNENCRYPTED_WIPE_COMMAND]:
                    time.sleep(gateway.settings.local_testing_mode * 0.1)
                    time.sleep(gateway.settings.data_diode_sockets * 1.5)
                    signal = WIPE if command == UNENCRYPTED_WIPE_COMMAND else EXIT
                    queues[EXIT_QUEUE].put(signal)
                continue

            # Buffered messages
            for onion_pub_key in m_buffer:
                if key_list.has_keyset(onion_pub_key) and m_buffer[onion_pub_key]:
                    send_packet(key_list, gateway, log_queue, *m_buffer[onion_pub_key].pop(0)[:-1])
                    continue

            # New messages
            if m_queue.qsize() != 0:
                queue_data    = m_queue.get()  # type: Tuple[bytes, bytes, bool, bool, bytes]
                onion_pub_key = queue_data[1]

                if key_list.has_keyset(onion_pub_key):
                    send_packet(key_list, gateway, log_queue, *queue_data[:-1])
                else:
                    m_buffer.setdefault(onion_pub_key, []).append(queue_data)
                continue

            # If traffic masking has been enabled, switch send mode when all queues are empty.
            if sm_queue.qsize() != 0 and all(q.qsize() == 0 for q in (km_queue, c_queue, rp_queue, m_queue)):
                settings = sm_queue.get()
                return settings, m_buffer

            time.sleep(0.01)