def main() -> None: """Load persistent settings and launch the Relay Program. This function loads settings from the settings database and launches processes for the Relay 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 `tfc.py` for Transmitter Program functionality. After you have reviewed the Transmitter Program's code, revisit the code of this program. The Relay Program operates multiple processes to enable real time IO between multiple data sources and destinations. Symbols: process_name denotes the name of the process ─>, <─, ↑, ↓ denotes the direction of data passed from one process to another (Description) denotes the description of data passed from one process to another ┈, ┊ denotes the link between a description and path of data matching the description ▶|, |◀ denotes the gateways where the direction of data flow is enforced with hardware data diodes Relay Program (Networked Computer) ┏━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━┓ ┃ (1: Onion Service private key) ┃ (2: Contact management commands) ┃ ┌──────────────────────────────┬────────────────────────┬────────────┐ ┃ │ │ ↓┈2 │ ┃ │ ┌─────> relay_command ┌───> c_req_manager │ ┃ │ │ │ │ │ ┃ │ (Relay Program┈│ (Onion Service┈│ │ │ ┃ │ commands) │ public key) │ │┈(In: Contact requests) │ ┃ │ │ ↓ │ ┊ 1┈↓ ┃ Source ───▶|─────(── gateway_loop ─> src_incoming ─> flask_server <─────> onion_service <───> client on contact's Computer ┃ │ │ ↑ ┊ ┃ Networked Computer │ (Local keys, commands, │ │ (Out: msg/file/pubkey/ ┃ │ and copies of messages)┄│ │ group mgmt message) ┃ │ │ │ ┃ │ ↓ │ ┃ Destination <──|◀─────(────────────────────── dst_outgoing │ Computer ┃ │ ┊ ↑ │ ┃ ├──> g_msg_manager ┊ │ │ ┃ │ ↑ ┊ │ │ ┃ │ (Group┈│ (Incoming┈│ (URL token)┈│ ┃ │ management │ messages) │ │ ┃ │ messages) │ │ │ ┃ ↓┈1,2 │ │ │ ┃ client_scheduler │ │ │ ┃ └──> client ──────────┴──────────────┘ ┃ ↑ ┃ │ ┃ └───────────────────────────────────────────────────────────> flask_server on ┃ ┊ ┃ contact's Networked (In: message/file/public key/group management message Computer ┃ Out: contact request) ┃ ┗━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━┛ The diagram above gives a rough overview of the structure of the Relay Program. The Relay Program acts as a protocol converter that reads datagrams from the Source Computer. Outgoing message/file/public key datagrams are made available in the user's Tor v3 Onion Service. Copies of sent message datagrams as well as datagrams from contacts' Onion Services are forwarded to the Destination Computer. The Relay-to-Relay encrypted datagrams from contacts such as contact requests, public keys and group management messages are displayed by the Relay Program. Outgoing message datagrams are loaded by contacts from the user's Flask web server. To request messages intended for them, each contact uses a contact-specific URL token to load the messages. The URL token is the X448 shared secret derived from the per-session ephemeral X448 values of the two conversing parties. The private value stays on the Relay Program -- the public value is obtained by connecting to the root domain of contact's Onion Service. """ if platform_is_tails(): working_dir = f'{os.getenv("HOME")}/{DIR_TAILS_PERS}{DIR_TFC}' else: working_dir = f'{os.getenv("HOME")}/{DIR_TFC}' ensure_dir(working_dir) os.chdir(working_dir) _, local_test, data_diode_sockets, qubes = process_arguments() gateway = Gateway(NC, local_test, data_diode_sockets, qubes) print_title(NC) url_token_private_key = X448.generate_private_key() url_token_public_key = X448.derive_public_key(url_token_private_key).hex() queues = \ {GATEWAY_QUEUE: Queue(), # All datagrams from `gateway_loop` to `src_incoming` DST_MESSAGE_QUEUE: Queue(), # Message datagrams from `src_incoming`/`client` to `dst_outgoing` TX_BUF_KEY_QUEUE: Queue(), # Datagram buffer key from `onion_service` to `src_incoming` RX_BUF_KEY_QUEUE: Queue(), # Datagram buffer key from `onion_service` to `flask_server` SRC_TO_RELAY_QUEUE: Queue(), # Command datagrams from `src_incoming` to `relay_command` DST_COMMAND_QUEUE: Queue(), # Command datagrams from `src_incoming` to `dst_outgoing` CONTACT_MGMT_QUEUE: Queue(), # Contact management commands from `relay_command` to `client_scheduler` C_REQ_STATE_QUEUE: Queue(), # Contact req. notify setting from `relay_command` to `c_req_manager` URL_TOKEN_QUEUE: Queue(), # URL tokens from `client` to `flask_server` GROUP_MSG_QUEUE: Queue(), # Group management messages from `client` to `g_msg_manager` CONTACT_REQ_QUEUE: Queue(), # Contact requests from `flask_server` to `c_req_manager` C_REQ_MGMT_QUEUE: Queue(), # Contact list management from `relay_command` to `c_req_manager` GROUP_MGMT_QUEUE: Queue(), # Contact list management from `relay_command` to `g_msg_manager` ONION_CLOSE_QUEUE: Queue(), # Onion Service close command from `relay_command` to `onion_service` ONION_KEY_QUEUE: Queue(), # Onion Service private key from `relay_command` to `onion_service` TOR_DATA_QUEUE: Queue(), # Open port for Tor from `onion_service` to `client_scheduler` EXIT_QUEUE: Queue(), # EXIT/WIPE signal from `relay_command` to `main` ACCOUNT_CHECK_QUEUE: Queue(), # Incorrectly typed accounts from `src_incoming` to `account_checker` ACCOUNT_SEND_QUEUE: Queue(), # Contact requests from `flask_server` to `account_checker` USER_ACCOUNT_QUEUE: Queue(), # User's public key from `onion_service` to `account_checker` PUB_KEY_CHECK_QUEUE: Queue(), # Typed public keys from `src_incoming` to `pub_key_checker` PUB_KEY_SEND_QUEUE: Queue(), # Received public keys from `client` to `pub_key_checker` GUI_INPUT_QUEUE: Queue() # User inputs from `GUI prompt` to `account_checker` } # type: Dict[bytes, Queue[Any]] process_list = [ Process(target=gateway_loop, args=(queues, gateway)), Process(target=src_incoming, args=(queues, gateway)), Process(target=dst_outgoing, args=(queues, gateway)), Process(target=client_scheduler, args=(queues, gateway, url_token_private_key)), Process(target=g_msg_manager, args=(queues, )), Process(target=c_req_manager, args=(queues, )), Process(target=flask_server, args=(queues, url_token_public_key)), Process(target=onion_service, args=(queues, )), Process(target=relay_command, args=( queues, gateway, )), Process(target=account_checker, args=(queues, sys.stdin.fileno())), Process(target=pub_key_checker, args=(queues, local_test)) ] for p in process_list: p.start() monitor_processes(process_list, NC, queues)
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)
def main() -> None: """Load persistent settings and launch the Relay Program. This function loads settings from the settings database and launches processes for the Relay 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 `tfc.py` for Transmitter Program functionality. After you have reviewed the Transmitter Program's code, revisit the code of this program. The Relay Program operates multiple processes to enable real time IO between multiple data sources and destinations. Symbols: process_name denotes the name of the process ─>, <─, ↑, ↓ denotes the direction of data passed from one process to another (Description) denotes the description of data passed from one process to another ┈, ┊ denotes the link between a description and path of data matching the description ▶|, |◀ denotes the gateways where the direction of data flow is enforced with hardware data diodes Relay Program (Networked Computer) ┏━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━┓ ┃ ┃ (Contact management commands) ┃ ┌─────────────────────────────┬─────────────────────┐ ┃ | | ↓ ┃ | ┌─────> relay_command ┌───> c_req_manager ┃ | │ │ | ┃ | │ (Onion Service┈│ |┈(Contact requests) ┃ | │ private key) │ | ┃ | │ ↓ | ┃ | │ onion_service ───────────────────────────> client on contact's ┃ | (Relay Program┈│ ↑ ┊ ┃ Networked Computer | commands) │ │┈(Outgoing msg/file/public key) ┃ | │ │ ┃ Source ───▶|─────(── gateway_loop ─> src_incoming ─> flask_server <─┐ Computer ┃ | | | ┃ | | | ┃ | (Local keys, commands, | | ┃ | and copies of messages)┄| | ┃ | ┊ ↓ | ┃ Destination <──|◀─────(────────────────────── dst_outgoing | Computer ┃ | ┊ ↑ | ┃ ├──> g_msg_manager ┊ │ | ┃ | ↑ ┊ │ | ┃ | (Group┈│ (Incoming┈│ (URL token)┈| ┃ | management │ messages) │ | ┃ │ messages) │ │ | ┃ ↓ │ │ | ┃ client_scheduler │ │ | ┃ └──> client ──────────┴─────────────────────┘ ┃ ↑ ┃ │ ┃ └─────────────────────────────────────────────────────────── flask_server on ┃ ┊ ┃ contact's Networked (Incoming message/file/public key/group management message) Computer ┃ ┃ ┗━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━┛ The diagram above gives a rough overview of the structure of the Relay Program. The Relay Program acts as a protocol converter that reads datagrams from the Source Computer. Outgoing message/file/public key datagrams are made available in the user's Tor v3 Onion Service. Copies of sent message datagrams as well as datagrams from contacts' Onion Services are forwarded to the Destination Computer. The Relay-to-Relay encrypted datagrams from contacts such as contact requests, public keys and group management messages are displayed by the Relay Program. Outgoing message datagrams are loaded by contacts from the user's Flask web server. To request messages intended for them, each contact uses a contact-specific URL token to load the messages. The URL token is the X448 shared secret derived from the per-session ephemeral X448 values of the two conversing parties. The private value stays on the Relay Program -- the public value is obtained by connecting to the root domain of contact's Onion Service. """ working_dir = f'{os.getenv("HOME")}/{DIR_TFC}' ensure_dir(working_dir) os.chdir(working_dir) _, local_test, data_diode_sockets = process_arguments() gateway = Gateway(NC, local_test, data_diode_sockets) print_title(NC) url_token_private_key = X448PrivateKey.generate() url_token_public_key = url_token_private_key.public_key().public_bytes( encoding=Encoding.Raw, format=PublicFormat.Raw).hex() # type: str queues = \ {GATEWAY_QUEUE: Queue(), # All datagrams from `gateway_loop` to `src_incoming` DST_MESSAGE_QUEUE: Queue(), # Message datagrams from `src_incoming`/`client` to `dst_outgoing` M_TO_FLASK_QUEUE: Queue(), # Message/pubkey datagrams from `src_incoming` to `flask_server` F_TO_FLASK_QUEUE: Queue(), # File datagrams from `src_incoming` to `flask_server` SRC_TO_RELAY_QUEUE: Queue(), # Command datagrams from `src_incoming` to `relay_command` DST_COMMAND_QUEUE: Queue(), # Command datagrams from `src_incoming` to `dst_outgoing` CONTACT_MGMT_QUEUE: Queue(), # Contact management commands from `relay_command` to `client_scheduler` C_REQ_STATE_QUEUE: Queue(), # Contact req. notify setting from `relay_command` to `c_req_manager` URL_TOKEN_QUEUE: Queue(), # URL tokens from `client` to `flask_server` GROUP_MSG_QUEUE: Queue(), # Group management messages from `client` to `g_msg_manager` CONTACT_REQ_QUEUE: Queue(), # Contact requests from `flask_server` to `c_req_manager` C_REQ_MGMT_QUEUE: Queue(), # Contact list management from `relay_command` to `c_req_manager` GROUP_MGMT_QUEUE: Queue(), # Contact list management from `relay_command` to `g_msg_manager` ONION_CLOSE_QUEUE: Queue(), # Onion Service close command from `relay_command` to `onion_service` ONION_KEY_QUEUE: Queue(), # Onion Service private key from `relay_command` to `onion_service` TOR_DATA_QUEUE: Queue(), # Open port for Tor from `onion_service` to `client_scheduler` EXIT_QUEUE: Queue() # EXIT/WIPE signal from `relay_command` to `main` } # type: Dict[bytes, Queue[Any]] process_list = [ Process(target=gateway_loop, args=(queues, gateway)), Process(target=src_incoming, args=(queues, gateway)), Process(target=dst_outgoing, args=(queues, gateway)), Process(target=client_scheduler, args=(queues, gateway, url_token_private_key)), Process(target=g_msg_manager, args=(queues, )), Process(target=c_req_manager, args=(queues, )), Process(target=flask_server, args=(queues, url_token_public_key)), Process(target=onion_service, args=(queues, )), Process(target=relay_command, args=(queues, gateway, sys.stdin.fileno())) ] for p in process_list: p.start() monitor_processes(process_list, NC, queues)