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
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)
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
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)