Пример #1
0
def validate_url_token(purp_url_token: str, queues: 'QueueDict',
                       pub_key_dict: 'PubKeyDict') -> bool:
    """Validate URL token using constant time comparison."""
    # This context manager hides the duration of URL_TOKEN_QUEUE check as
    # well as the number of accounts in pub_key_dict when iterating over keys.
    with HideRunTime(duration=0.01):

        # Check if the client has derived new URL token for contact(s).
        # If yes, add the url tokens to pub_key_dict to have up-to-date
        # information about whether the purported URL tokens are valid.
        while queues[URL_TOKEN_QUEUE].qsize() > 0:
            onion_pub_key, url_token = queues[URL_TOKEN_QUEUE].get()

            # To keep dictionary compact, delete old key when new
            # one with matching value (onion_pub_key) is received.
            for ut in list(pub_key_dict.keys()):
                if pub_key_dict[ut] == onion_pub_key:
                    del pub_key_dict[ut]

            pub_key_dict[url_token] = onion_pub_key

        # Here we OR the result of constant time comparison with initial
        # False. ORing is also a constant time operation that returns
        # True if a matching shared secret was found in pub_key_dict.
        valid_url_token = False
        for url_token in pub_key_dict:
            try:
                valid_url_token |= secrets.compare_digest(
                    purp_url_token, url_token)
            except TypeError:
                valid_url_token |= False

    return valid_url_token
Пример #2
0
 def test_static_time(self) -> None:
     start = time.monotonic()
     with HideRunTime(self.settings, duration=1):
         pass
     duration = time.monotonic() - start
     self.assertTrue(0.9 < duration < 1.1)
Пример #3
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
Пример #4
0
 def test_traffic_masking_delay(self) -> None:
     start = time.monotonic()
     with HideRunTime(self.settings, delay_type=TRAFFIC_MASKING):
         pass
     duration = time.monotonic() - start
     self.assertTrue(duration > self.settings.tm_static_delay)