258 lines
10 KiB
Python
258 lines
10 KiB
Python
#!/usr/bin/env python3.7
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
TFC - Onion-routed, endpoint secure messaging system
|
|
Copyright (C) 2013-2020 Markus Ottela
|
|
|
|
This file is part of TFC.
|
|
|
|
TFC is free software: you can redistribute it and/or modify it under the terms
|
|
of the GNU General Public License as published by the Free Software Foundation,
|
|
either version 3 of the License, or (at your option) any later version.
|
|
|
|
TFC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with TFC. If not, see <https://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import serial
|
|
import time
|
|
import typing
|
|
|
|
from typing import Any, Dict
|
|
|
|
from src.common.encoding import bytes_to_bool, bytes_to_int
|
|
from src.common.exceptions import SoftError
|
|
from src.common.misc import ignored, reset_terminal, separate_header, separate_headers, split_byte_string
|
|
from src.common.output import clear_screen, m_print
|
|
from src.common.statics import (ACCOUNT_CHECK_QUEUE, CONFIRM_CODE_LENGTH, CONTACT_MGMT_QUEUE, C_REQ_MGMT_QUEUE,
|
|
C_REQ_STATE_QUEUE, ENCODED_BOOLEAN_LENGTH, ENCODED_INTEGER_LENGTH, EXIT,
|
|
GROUP_MGMT_QUEUE, LOCAL_TESTING_PACKET_DELAY, MAX_INT, ONION_CLOSE_QUEUE,
|
|
ONION_KEY_QUEUE, ONION_SERVICE_PRIVATE_KEY_LENGTH, ONION_SERVICE_PUBLIC_KEY_LENGTH,
|
|
PUB_KEY_CHECK_QUEUE, RP_ADD_CONTACT_HEADER, RP_REMOVE_CONTACT_HEADER,
|
|
SRC_TO_RELAY_QUEUE, UNENCRYPTED_ACCOUNT_CHECK, UNENCRYPTED_ADD_EXISTING_CONTACT,
|
|
UNENCRYPTED_ADD_NEW_CONTACT, UNENCRYPTED_BAUDRATE, UNENCRYPTED_COMMAND_HEADER_LENGTH,
|
|
UNENCRYPTED_EC_RATIO, UNENCRYPTED_EXIT_COMMAND, UNENCRYPTED_MANAGE_CONTACT_REQ,
|
|
UNENCRYPTED_ONION_SERVICE_DATA, UNENCRYPTED_PUBKEY_CHECK, UNENCRYPTED_REM_CONTACT,
|
|
UNENCRYPTED_SCREEN_CLEAR, UNENCRYPTED_SCREEN_RESET, UNENCRYPTED_WIPE_COMMAND, WIPE)
|
|
|
|
if typing.TYPE_CHECKING:
|
|
from multiprocessing import Queue
|
|
from src.common.gateway import Gateway
|
|
QueueDict = Dict[bytes, Queue[Any]]
|
|
|
|
|
|
def relay_command(queues: 'QueueDict',
|
|
gateway: 'Gateway',
|
|
unit_test: bool = False
|
|
) -> None:
|
|
"""Process Relay Program commands."""
|
|
queue_from_src = queues[SRC_TO_RELAY_QUEUE]
|
|
|
|
while True:
|
|
with ignored(EOFError, KeyboardInterrupt, SoftError):
|
|
while queue_from_src.qsize() == 0:
|
|
time.sleep(0.01)
|
|
|
|
command = queue_from_src.get()
|
|
process_command(command, gateway, queues)
|
|
|
|
if unit_test:
|
|
break
|
|
|
|
|
|
def process_command(command: bytes,
|
|
gateway: 'Gateway',
|
|
queues: 'QueueDict'
|
|
) -> None:
|
|
"""Select function for received Relay Program command."""
|
|
header, command = separate_header(command, UNENCRYPTED_COMMAND_HEADER_LENGTH)
|
|
|
|
# Header Function to run ( Parameters )
|
|
# ---------------------------------------------------------------------------------
|
|
function_d = {UNENCRYPTED_SCREEN_CLEAR: (clear_windows, gateway, ),
|
|
UNENCRYPTED_SCREEN_RESET: (reset_windows, gateway, ),
|
|
UNENCRYPTED_EXIT_COMMAND: (exit_tfc, gateway, queues),
|
|
UNENCRYPTED_WIPE_COMMAND: (wipe, gateway, queues),
|
|
UNENCRYPTED_EC_RATIO: (change_ec_ratio, command, gateway, ),
|
|
UNENCRYPTED_BAUDRATE: (change_baudrate, command, gateway, ),
|
|
UNENCRYPTED_MANAGE_CONTACT_REQ: (manage_contact_req, command, queues),
|
|
UNENCRYPTED_ADD_NEW_CONTACT: (add_contact, command, queues, False ),
|
|
UNENCRYPTED_ADD_EXISTING_CONTACT: (add_contact, command, queues, True ),
|
|
UNENCRYPTED_REM_CONTACT: (remove_contact, command, queues),
|
|
UNENCRYPTED_ONION_SERVICE_DATA: (add_onion_data, command, queues),
|
|
UNENCRYPTED_ACCOUNT_CHECK: (compare_accounts, command, queues),
|
|
UNENCRYPTED_PUBKEY_CHECK: (compare_pub_keys, command, queues)
|
|
} # type: Dict[bytes, Any]
|
|
|
|
if header not in function_d:
|
|
raise SoftError("Error: Received an invalid command.")
|
|
|
|
from_dict = function_d[header]
|
|
func = from_dict[0]
|
|
parameters = from_dict[1:]
|
|
func(*parameters)
|
|
|
|
|
|
def race_condition_delay(gateway: 'Gateway') -> None:
|
|
"""Prevent race condition with Receiver command."""
|
|
if gateway.settings.local_testing_mode:
|
|
time.sleep(LOCAL_TESTING_PACKET_DELAY)
|
|
time.sleep(gateway.settings.data_diode_sockets * 1.0)
|
|
|
|
|
|
def clear_windows(gateway: 'Gateway') -> None:
|
|
"""Clear Relay Program screen."""
|
|
race_condition_delay(gateway)
|
|
clear_screen()
|
|
|
|
|
|
def reset_windows(gateway: 'Gateway') -> None:
|
|
"""Reset Relay Program screen."""
|
|
race_condition_delay(gateway)
|
|
reset_terminal()
|
|
|
|
|
|
def exit_tfc(gateway: 'Gateway', queues: 'QueueDict') -> None:
|
|
"""Exit TFC.
|
|
|
|
The queue is read by
|
|
relay.onion.onion_service()
|
|
"""
|
|
race_condition_delay(gateway)
|
|
queues[ONION_CLOSE_QUEUE].put(EXIT)
|
|
|
|
|
|
def wipe(gateway: 'Gateway', queues: 'QueueDict') -> None:
|
|
"""Reset terminal, wipe all user data and power off the system.
|
|
|
|
No effective RAM overwriting tool currently exists, so as long as Source and
|
|
Destination Computers use FDE and DDR3 memory, recovery of user data becomes
|
|
impossible very fast:
|
|
https://www1.cs.fau.de/filepool/projects/coldboot/fares_coldboot.pdf
|
|
|
|
The queue is read by
|
|
relay.onion.onion_service()
|
|
"""
|
|
reset_terminal()
|
|
race_condition_delay(gateway)
|
|
queues[ONION_CLOSE_QUEUE].put(WIPE)
|
|
|
|
|
|
def change_ec_ratio(command: bytes, gateway: 'Gateway') -> None:
|
|
"""Change Relay Program's Reed-Solomon error correction ratio."""
|
|
try:
|
|
value = int(command)
|
|
if value < 0 or value > MAX_INT:
|
|
raise ValueError
|
|
except ValueError:
|
|
raise SoftError("Error: Received invalid EC ratio value from Transmitter Program.")
|
|
|
|
m_print("Error correction ratio will change on restart.", head=1, tail=1)
|
|
|
|
gateway.settings.serial_error_correction = value
|
|
gateway.settings.store_settings()
|
|
|
|
|
|
def change_baudrate(command: bytes, gateway: 'Gateway') -> None:
|
|
"""Change Relay Program's serial interface baud rate setting."""
|
|
try:
|
|
value = int(command)
|
|
if value not in serial.Serial.BAUDRATES:
|
|
raise ValueError
|
|
except ValueError:
|
|
raise SoftError("Error: Received invalid baud rate value from Transmitter Program.")
|
|
|
|
m_print("Baud rate will change on restart.", head=1, tail=1)
|
|
|
|
gateway.settings.serial_baudrate = value
|
|
gateway.settings.store_settings()
|
|
|
|
|
|
def manage_contact_req(command: bytes,
|
|
queues: 'QueueDict',
|
|
notify: bool = True
|
|
) -> None:
|
|
"""Control whether contact requests are accepted."""
|
|
enabled = bytes_to_bool(command)
|
|
if notify:
|
|
m_print(f"Contact requests are have been {('enabled' if enabled else 'disabled')}.", head=1, tail=1)
|
|
queues[C_REQ_STATE_QUEUE].put(enabled)
|
|
|
|
|
|
def add_contact(command: bytes,
|
|
queues: 'QueueDict',
|
|
existing: bool,
|
|
) -> None:
|
|
"""Add clients to Relay Program.
|
|
|
|
The queues are read by
|
|
relay.client.client_scheduler()
|
|
relay.client.g_msg_manager() and
|
|
relay.client.c_req_manager()
|
|
"""
|
|
queues[CONTACT_MGMT_QUEUE].put((RP_ADD_CONTACT_HEADER, command, existing))
|
|
queues[GROUP_MGMT_QUEUE].put((RP_ADD_CONTACT_HEADER, command))
|
|
queues[C_REQ_MGMT_QUEUE].put((RP_ADD_CONTACT_HEADER, command))
|
|
|
|
|
|
def remove_contact(command: bytes, queues: 'QueueDict') -> None:
|
|
"""Remove clients from Relay Program.
|
|
|
|
The queues are read by
|
|
relay.client.client_scheduler()
|
|
relay.client.g_msg_manager() and
|
|
relay.client.c_req_manager()
|
|
"""
|
|
queues[CONTACT_MGMT_QUEUE].put((RP_REMOVE_CONTACT_HEADER, command, False))
|
|
queues[GROUP_MGMT_QUEUE].put((RP_REMOVE_CONTACT_HEADER, command))
|
|
queues[C_REQ_MGMT_QUEUE].put((RP_REMOVE_CONTACT_HEADER, command))
|
|
|
|
|
|
def add_onion_data(command: bytes, queues: 'QueueDict') -> None:
|
|
"""Add Onion Service data.
|
|
|
|
Separate onion service private key and public keys for
|
|
pending/existing contacts and add them as contacts.
|
|
|
|
The ONION_KEY_QUEUE is read by
|
|
relay.onion.onion_service()
|
|
"""
|
|
os_private_key, confirmation_code, allow_req_byte, no_pending_bytes, ser_pub_keys \
|
|
= separate_headers(command, [ONION_SERVICE_PRIVATE_KEY_LENGTH, CONFIRM_CODE_LENGTH,
|
|
ENCODED_BOOLEAN_LENGTH, ENCODED_INTEGER_LENGTH])
|
|
|
|
no_pending = bytes_to_int(no_pending_bytes)
|
|
public_key_list = split_byte_string(ser_pub_keys, ONION_SERVICE_PUBLIC_KEY_LENGTH)
|
|
pending_public_keys = public_key_list[:no_pending]
|
|
existing_public_keys = public_key_list[no_pending:]
|
|
|
|
for onion_pub_key in pending_public_keys:
|
|
add_contact(onion_pub_key, queues, existing=False)
|
|
for onion_pub_key in existing_public_keys:
|
|
add_contact(onion_pub_key, queues, existing=True)
|
|
|
|
manage_contact_req(allow_req_byte, queues, notify=False)
|
|
queues[ONION_KEY_QUEUE].put((os_private_key, confirmation_code))
|
|
|
|
|
|
def compare_accounts(command: bytes, queues: 'QueueDict') -> None:
|
|
"""\
|
|
Compare incorrectly typed account to what's available on Relay
|
|
Program.
|
|
"""
|
|
queues[ACCOUNT_CHECK_QUEUE].put(command.decode())
|
|
|
|
|
|
def compare_pub_keys(command: bytes, queues: 'QueueDict') -> None:
|
|
"""\
|
|
Compare incorrectly typed public key to what's available on Relay
|
|
Program.
|
|
"""
|
|
account, incorrect_pub_key = separate_header(command, ONION_SERVICE_PUBLIC_KEY_LENGTH)
|
|
queues[PUB_KEY_CHECK_QUEUE].put((account, incorrect_pub_key))
|