コード例 #1
0
ファイル: test_gateway.py プロジェクト: XorgX304/tfc
 def test_read_serial(self, *_) -> None:
     gateway = Gateway(operation=RX,
                       local_test=False,
                       dd_sockets=False,
                       qubes=False)
     data = gateway.read()
     self.assertEqual(data, b"12")
コード例 #2
0
ファイル: test_gateway.py プロジェクト: xprog12/tfc
    def test_local_testing_write(self, *_: Any) -> None:
        gateway = Gateway(operation=TX, local_test=True, dd_sockets=False)

        self.assertIsNone(gateway.write(b'data'))

        with self.assertRaises(SystemExit):
            gateway.write(b'data')
コード例 #3
0
ファイル: test_gateway.py プロジェクト: dimwap/tfc
    def test_qubes_read_file(self, *_: Any) -> None:
        # Setup
        ensure_dir(f"{QUBES_BUFFER_INCOMING_DIR}/")

        def packet_delayer() -> None:
            """Create packets one at a time."""
            time.sleep(0.1)

            with open(f"{QUBES_BUFFER_INCOMING_DIR}/{QUBES_BUFFER_INCOMING_PACKET}.invalid", 'wb+') as fp:
                fp.write(base64.b85encode(b'data'))

            time.sleep(0.1)

            with open(f"{QUBES_BUFFER_INCOMING_DIR}/{QUBES_BUFFER_INCOMING_PACKET}.0", 'wb+') as fp:
                fp.write(base64.b85encode(b'data'))

        threading.Thread(target=packet_delayer).start()

        gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=True)

        # Test
        self.assert_se("No packet was available.", gateway.read)

        time.sleep(0.3)

        self.assertIsInstance(gateway, Gateway)
        self.assertEqual(gateway.read(), b'data')

        # Test invalid packet content is handled
        with open(f"{QUBES_BUFFER_INCOMING_DIR}/{QUBES_BUFFER_INCOMING_PACKET}.1", 'wb+') as f:
            f.write(os.urandom(32))
        self.assert_se("Error: Received packet had invalid Base85 encoding.", gateway.read)
コード例 #4
0
ファイル: test_gateway.py プロジェクト: XorgX304/tfc
 def test_local_testing_read(self, *_: Any) -> None:
     gateway = Gateway(operation=RX,
                       local_test=True,
                       dd_sockets=False,
                       qubes=False)
     self.assertEqual(gateway.read(), b'data')
     with self.assertRaises(SystemExit):
         gateway.read()
コード例 #5
0
ファイル: test_gateway.py プロジェクト: barleyj/tfc
    def test_class(self):
        # Setup
        settings = Settings()
        gateway = Gateway(settings)

        # Test
        self.assertIsNone(gateway.write(b'test'))
        self.assertEqual(gateway.search_serial_interface(), '/dev/ttyS0')
コード例 #6
0
ファイル: test_gateway.py プロジェクト: savg110/tfc
    def test_qubes_socket_server_raises_critical_error_if_interface_is_not_initialized(self, *_: Any) -> None:
        # Setup
        gateway            = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=True)
        gateway.rxq_socket = None

        # Test
        with self.assertRaises(SystemExit):
            self.assertEqual(gateway.read(), b'data')
コード例 #7
0
ファイル: test_gateway.py プロジェクト: dimwap/tfc
    def test_serial_uninitialized_socket_interface_for_read_raises_critical_error(self, *_) -> None:
        # Setup
        gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
        gateway.rx_socket = None

        # Test
        with self.assertRaises(SystemExit):
            gateway.read()
コード例 #8
0
ファイル: test_gateway.py プロジェクト: AJMartel/tfc
    def setUp(self):
        self.settings = Settings(session_usb_serial_adapter=True)
        self.o_listdir = os.listdir
        self.o_serial = serial.Serial

        input_list = ['ttyUSB0', 'ttyS0', 'ttyUSB0', 'ttyS0', 'ttyUSB0']
        gen = iter(input_list)
        os.listdir = lambda _: [next(gen)]
        serial.Serial = TestGatewaySerial.MockSerial
        self.gateway = Gateway(self.settings)
コード例 #9
0
ファイル: test_gateway.py プロジェクト: XorgX304/tfc
    def test_socket_server(self, *_: Any) -> None:
        gateway = Gateway(operation=RX,
                          local_test=True,
                          dd_sockets=False,
                          qubes=False)
        self.assertIsInstance(gateway, Gateway)

        with self.assertRaises(SystemExit):
            Gateway(operation=RX,
                    local_test=True,
                    dd_sockets=False,
                    qubes=False)
コード例 #10
0
ファイル: test_gateway.py プロジェクト: dimwap/tfc
    def test_add_error_correction(self, *_) -> None:
        gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
        packet = b"packet"

        # Test BLAKE2b based checksum
        gateway.settings.session_serial_error_correction = 0
        self.assertEqual(gateway.add_error_correction(packet),
                         packet + blake2b(packet, digest_size=PACKET_CHECKSUM_LENGTH))

        # Test Reed-Solomon erasure code
        gateway.settings.session_serial_error_correction = 5
        gateway.rs = RSCodec(gateway.settings.session_serial_error_correction)
        self.assertEqual(gateway.add_error_correction(packet),
                         gateway.rs.encode(packet))
コード例 #11
0
ファイル: test_gateway.py プロジェクト: XorgX304/tfc
 def test_qubes_send_to_destinationVM(self, mock_popen) -> None:
     gateway = Gateway(operation=NC,
                       local_test=False,
                       dd_sockets=False,
                       qubes=True)
     self.assertIsInstance(gateway, Gateway)
     self.assertIsNone(gateway.write(b'data'))
     mock_popen.assert_called_with([
         '/usr/bin/qrexec-client-vm', QUBES_DST_VM_NAME,
         QUBES_NET_DST_POLICY
     ],
                                   stderr=-3,
                                   stdin=-1,
                                   stdout=-3)
コード例 #12
0
ファイル: test_gateway.py プロジェクト: dimwap/tfc
    def test_loop(self, _: Any) -> None:
        gateway = Gateway(operation=RX, local_test=True, dd_sockets=False, qubes=False)
        self.assertIsNone(gateway_loop(self.queues, gateway, unit_test=True))

        data = self.queues[GATEWAY_QUEUE].get()
        self.assertIsInstance(data[0], datetime)
        self.assertEqual(data[1], 'message')
コード例 #13
0
ファイル: test_gateway.py プロジェクト: AJMartel/tfc
class TestMultiProcessingServer(unittest.TestCase):
    class MockMultiprocessingListener(object):
        def __init__(self, args):
            self.hostname = args[0]
            self.socket_no = args[1]
            self.written = []

        def accept(self):
            class Interface(object):
                def __init__(self, hostname, socket_no):
                    self.hostname = hostname
                    self.socket_no = socket_no

                @staticmethod
                def recv():
                    return b'mock_message'

            return Interface(self.hostname, self.socket_no)

    def setUp(self):
        self.settings = Settings(software_operation=RX,
                                 local_testing_mode=True)
        multiprocessing.connection.Listener = TestMultiProcessingServer.MockMultiprocessingListener
        self.gateway = Gateway(self.settings)

    def test_listener(self):
        self.assertEqual(self.gateway.interface.socket_no, RXM_LISTEN_SOCKET)
        self.assertEqual(self.gateway.interface.hostname, 'localhost')
        self.assertEqual(self.gateway.read(), b'mock_message')
コード例 #14
0
ファイル: test_gateway.py プロジェクト: XorgX304/tfc
 def test_search_and_establish_serial(self, *_: Any) -> None:
     gateway = Gateway(operation=RX,
                       local_test=False,
                       dd_sockets=False,
                       qubes=False)
     self.assertIsInstance(gateway.rs, RSCodec)
     self.assertIs(gateway.tx_serial, gateway.rx_serial)
コード例 #15
0
ファイル: test_gateway.py プロジェクト: xprog12/tfc
    def test_detect_errors(self, *_: Any) -> None:
        gateway = Gateway(operation=RX, local_test=False, dd_sockets=False)
        packet = b'packet'

        # Test BLAKE2b based checksum
        gateway.settings.session_serial_error_correction = 0
        self.assertEqual(
            gateway.detect_errors(gateway.add_error_correction(packet)),
            packet)

        # Test unrecoverable error raises FR
        self.assert_se("Warning! Received packet had an invalid checksum.",
                       gateway.detect_errors, 300 * b'a')

        # Test Reed-Solomon erasure code
        gateway.settings.session_serial_error_correction = 5
        gateway.rs = RSCodec(gateway.settings.session_serial_error_correction)
        self.assertEqual(
            gateway.detect_errors(gateway.add_error_correction(packet)),
            packet)

        # Test unrecoverable error raises FR
        self.assert_se(
            "Error: Reed-Solomon failed to correct errors in the received packet.",
            gateway.detect_errors, 300 * b'a')
コード例 #16
0
ファイル: test_gateway.py プロジェクト: XorgX304/tfc
    def test_detect_errors(self, *_: Any) -> None:
        gateway = Gateway(operation=RX,
                          local_test=False,
                          dd_sockets=False,
                          qubes=False)
        packet = b'packet'

        # Test BLAKE2b based checksum
        gateway.settings.session_serial_error_correction = 0
        self.assertEqual(
            gateway.detect_errors(gateway.add_error_correction(packet)),
            packet)

        # Test unrecoverable error raises SoftError
        self.assert_se("Warning! Received packet had an invalid checksum.",
                       gateway.detect_errors, 300 * b'a')

        # Test Reed-Solomon erasure code
        gateway.settings.session_serial_error_correction = 5
        gateway.rs = RSCodec(gateway.settings.session_serial_error_correction)
        self.assertEqual(
            gateway.detect_errors(gateway.add_error_correction(packet)),
            packet)

        # Test unrecoverable error raises SoftError
        self.assert_se(
            "Error: Reed-Solomon failed to correct errors in the received packet.",
            gateway.detect_errors, 300 * b'a')

        # Qubes

        # Test with B58 encoding
        gateway.settings.qubes = True
        packet_with_error_correction = base64.b85encode(
            gateway.add_error_correction(packet))
        self.assertEqual(gateway.detect_errors(packet_with_error_correction),
                         packet)

        # Test invalid B85 encoding raises SoftError
        packet_with_error_correction = base64.b85encode(
            gateway.add_error_correction(packet))
        packet_with_error_correction += b'\x00'
        self.assert_se("Error: Received packet had invalid Base85 encoding.",
                       gateway.detect_errors, packet_with_error_correction)
        gateway.settings.qubes = False
コード例 #17
0
ファイル: test_gateway.py プロジェクト: savg110/tfc
    def test_qubes_auto_config_from_file(self, *_: Any) -> None:
        # Setup
        test_ip = '10.137.0.17'
        open(QUBES_RX_IP_ADDR_FILE, 'w+').write(test_ip)

        # Test
        self.assertTrue(os.path.isfile(QUBES_RX_IP_ADDR_FILE))
        gateway = Gateway(operation=TX, local_test=False, dd_sockets=False, qubes=True)
        self.assertEqual(gateway.settings.rx_udp_ip, test_ip)
        self.assertFalse(os.path.isfile(QUBES_RX_IP_ADDR_FILE))
コード例 #18
0
ファイル: test_gateway.py プロジェクト: dimwap/tfc
    def test_search_serial_interfaces(self, *_: Any) -> None:
        gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)

        interface = gateway.search_serial_interface()
        self.assertEqual(interface, '/dev/ttyUSB0')

        # Test unavailable system serial exits:
        gateway.settings.session_usb_serial_adapter = False

        interface = gateway.search_serial_interface()
        self.assertEqual(interface, '/dev/ttyS0')

        with self.assertRaises(SystemExit):
            gateway.search_serial_interface()
コード例 #19
0
ファイル: test_gateway.py プロジェクト: AJMartel/tfc
class TestMultiProcessingClient(unittest.TestCase):
    class MockMultiprocessingClient(object):
        def __init__(self, args):
            self.hostname = args[0]
            self.socket_no = args[1]
            self.written = []

        def send(self, output):
            self.written.append(output)

    def setUp(self):
        self.settings = Settings(software_operation=TX,
                                 local_testing_mode=True)
        multiprocessing.connection.Client = TestMultiProcessingClient.MockMultiprocessingClient
        self.gateway = Gateway(self.settings)

    def test_socket(self):
        self.assertEqual(self.gateway.interface.socket_no, NH_LISTEN_SOCKET)
        self.assertEqual(self.gateway.interface.hostname, 'localhost')
        self.assertIsNone(self.gateway.write(b'test'))
        self.assertEqual(self.gateway.interface.written[0], b'test')
コード例 #20
0
ファイル: test_gateway.py プロジェクト: AJMartel/tfc
class TestGatewaySerial(unittest.TestCase):
    class MockSerial(object):
        def __init__(self, iface_name, baudrate, timeout):
            self.iface = iface_name
            self.baudrate = baudrate
            self.timeout = timeout
            self.written = []
            output_list = [b'', bytearray(b'a'), bytearray(b'b'), b'']
            self.gen = iter(output_list)

        def write(self, output):
            self.written.append(output)

        def read(self, _):
            time.sleep(0.1)
            return next(self.gen)

        def flush(self):
            pass

    def setUp(self):
        self.settings = Settings(session_usb_serial_adapter=True)
        self.o_listdir = os.listdir
        self.o_serial = serial.Serial

        input_list = ['ttyUSB0', 'ttyS0', 'ttyUSB0', 'ttyS0', 'ttyUSB0']
        gen = iter(input_list)
        os.listdir = lambda _: [next(gen)]
        serial.Serial = TestGatewaySerial.MockSerial
        self.gateway = Gateway(self.settings)

    def tearDown(self):
        os.listdir = self.o_listdir
        serial.Serial = self.o_serial

    def test_serial(self):
        self.assertIsNone(self.gateway.write(b'test'))
        self.assertEqual(self.gateway.search_serial_interface(),
                         '/dev/ttyUSB0')
        self.assertEqual(self.gateway.read(), b'ab')

        self.gateway.settings.session_usb_serial_adapter = False
        self.assertEqual(self.gateway.search_serial_interface(), '/dev/ttyS0')

        with self.assertRaises(SystemExit):
            self.gateway.search_serial_interface()
コード例 #21
0
ファイル: test_gateway.py プロジェクト: dimwap/tfc
 def test_serial_exception_during_establish_exists(self, *_: Any) -> None:
     with self.assertRaises(SystemExit):
         Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
コード例 #22
0
ファイル: test_gateway.py プロジェクト: dimwap/tfc
 def test_socket_client(self, *_: Any) -> None:
     gateway = Gateway(operation=TX, local_test=True, dd_sockets=False, qubes=False)
     self.assertIsInstance(gateway, Gateway)
コード例 #23
0
def main() -> None:
    """Derive master key, decrypt databases and initialize processes."""
    os.chdir(sys.path[0])

    check_kernel_version()
    check_kernel_entropy()

    operation, local_test, dd_sockets = process_arguments()

    clear_screen()
    c_print(TFC, head=1, tail=1)

    master_key = MasterKey(operation, local_test)
    settings = Settings(master_key, operation, local_test, dd_sockets)
    contact_list = ContactList(master_key, settings)
    key_list = KeyList(master_key, settings)
    group_list = GroupList(master_key, settings, contact_list)
    gateway = Gateway(settings)

    if settings.software_operation == TX:
        queues = {
            MESSAGE_PACKET_QUEUE: Queue(),
            FILE_PACKET_QUEUE: Queue(),
            COMMAND_PACKET_QUEUE: Queue(),
            NH_PACKET_QUEUE: Queue(),
            LOG_PACKET_QUEUE: Queue(),
            EXIT_QUEUE: Queue(),
            NOISE_PACKET_QUEUE: Queue(),
            NOISE_COMMAND_QUEUE: Queue(),
            KEY_MANAGEMENT_QUEUE: Queue(),
            WINDOW_SELECT_QUEUE: Queue()
        }

        process_list = [
            Process(target=input_loop,
                    args=(queues, settings, gateway, contact_list, group_list,
                          master_key, sys.stdin.fileno())),
            Process(target=sender_loop,
                    args=(queues, settings, gateway, key_list)),
            Process(target=log_writer_loop, args=(queues, ))
        ]

        if settings.session_traffic_masking:
            process_list.extend([
                Process(target=noise_loop,
                        args=(P_N_HEADER, queues[NOISE_PACKET_QUEUE],
                              contact_list)),
                Process(target=noise_loop,
                        args=(C_N_HEADER, queues[NOISE_COMMAND_QUEUE]))
            ])

    else:
        queues = {
            LOCAL_KEY_PACKET_HEADER: Queue(),
            PUBLIC_KEY_PACKET_HEADER: Queue(),
            MESSAGE_PACKET_HEADER: Queue(),
            COMMAND_PACKET_HEADER: Queue(),
            IMPORTED_FILE_HEADER: Queue(),
            EXIT_QUEUE: Queue(),
            GATEWAY_QUEUE: Queue()
        }

        process_list = [
            Process(target=gateway_loop, args=(queues, gateway)),
            Process(target=receiver_loop, args=(queues, settings)),
            Process(target=output_loop,
                    args=(queues, settings, contact_list, key_list, group_list,
                          master_key, sys.stdin.fileno()))
        ]

    for p in process_list:
        p.start()

    while True:
        with ignored(EOFError, KeyboardInterrupt):
            time.sleep(0.1)
            if not all([p.is_alive() for p in process_list]):
                for p in process_list:
                    p.terminate()
                sys.exit(1)

            if not queues[EXIT_QUEUE].empty():
                command = queues[EXIT_QUEUE].get()
                for p in process_list:
                    p.terminate()
                if command == WIPE:
                    subprocess.Popen(
                        f"find {DIR_USER_DATA} -name '{operation}*' -type f -exec shred -n 3 -z -u {{}} \;",
                        shell=True).wait()
                    os.system('poweroff')
                else:
                    sys.exit(0)
コード例 #24
0
ファイル: test_gateway.py プロジェクト: dimwap/tfc
 def test_establish_local_testing_gateway(self, *_: Any) -> None:
     gateway = Gateway(operation=NC, local_test=True, dd_sockets=False, qubes=False)
     self.assertIsInstance(gateway.rs, RSCodec)
コード例 #25
0
ファイル: test_gateway.py プロジェクト: dimwap/tfc
 def test_keyboard_interrupt_exits(self, *_: Any) -> None:
     with self.assertRaises(SystemExit):
         Gateway(operation=TX, local_test=True, dd_sockets=False, qubes=False)
コード例 #26
0
ファイル: tfc.py プロジェクト: barleyj/tfc
def main() -> None:
    """Derive master key, decrypt databases and initialize processes."""
    os.chdir(sys.path[0])
    init_entropy()

    operation, local_test, dd_sockets = process_arguments()

    clear_screen()
    c_print("TFC", head=1, tail=1)

    master_key = MasterKey(operation, local_test)
    settings = Settings(master_key, operation, local_test, dd_sockets)
    contact_list = ContactList(master_key, settings)
    key_list = KeyList(master_key, settings)
    group_list = GroupList(master_key, settings, contact_list)
    gateway = Gateway(settings)
    process_list = []

    if settings.software_operation == 'tx':

        queues = {
            MESSAGE_PACKET_QUEUE: Queue(),
            FILE_PACKET_QUEUE: Queue(),
            COMMAND_PACKET_QUEUE: Queue(),
            LOG_PACKET_QUEUE: Queue(),
            NOISE_PACKET_QUEUE: Queue(),
            NOISE_COMMAND_QUEUE: Queue(),
            KEY_MANAGEMENT_QUEUE: Queue(),
            WINDOW_SELECT_QUEUE: Queue()
        }

        if settings.session_trickle:
            np_filler = Process(target=noise_process,
                                args=(P_N_HEADER, queues[NOISE_PACKET_QUEUE],
                                      contact_list))
            nc_filler = Process(target=noise_process,
                                args=(C_N_HEADER, queues[NOISE_COMMAND_QUEUE]))
            process_list.extend([np_filler, nc_filler])
            for p in [np_filler, nc_filler]:
                p.start()
            while any([
                    q.qsize() < 1000 for q in
                [queues[NOISE_PACKET_QUEUE], queues[NOISE_COMMAND_QUEUE]]
            ]):
                time.sleep(0.1)

        sender_process = Process(target=sender_loop,
                                 args=(settings, queues, gateway, key_list))
        input_process = Process(target=tx_loop,
                                args=(settings, queues, gateway,
                                      contact_list, group_list, master_key,
                                      sys.stdin.fileno()))
        log_process = Process(target=log_writer,
                              args=(queues[LOG_PACKET_QUEUE], ))
        process_list.extend([sender_process, input_process, log_process])
        for p in [sender_process, input_process, log_process]:
            p.start()

    elif settings.software_operation == 'rx':

        queues = {
            LOCAL_KEY_PACKET_HEADER: Queue(),
            PUBLIC_KEY_PACKET_HEADER: Queue(),
            MESSAGE_PACKET_HEADER: Queue(),
            COMMAND_PACKET_HEADER: Queue(),
            IMPORTED_FILE_CT_HEADER: Queue(),
            GATEWAY_QUEUE: Queue()
        }

        gateway_process = Process(target=gw_incoming,
                                  args=(gateway, queues[GATEWAY_QUEUE]))
        receiver_process = Process(target=receiver_loop,
                                   args=(settings, queues))
        output_process = Process(target=rx_loop,
                                 args=(settings, queues, contact_list,
                                       key_list, group_list, master_key,
                                       sys.stdin.fileno()))
        process_list.extend(
            [gateway_process, receiver_process, output_process])
        for p in [gateway_process, receiver_process, output_process]:
            p.start()

    while True:
        try:
            time.sleep(0.1)
            if not all([p.is_alive() for p in process_list]):
                for p in process_list:
                    p.terminate()
                exit()
        except (EOFError, KeyboardInterrupt):
            pass
コード例 #27
0
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)
コード例 #28
0
ファイル: relay.py プロジェクト: dimwap/tfc
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)
コード例 #29
0
ファイル: test_gateway.py プロジェクト: dimwap/tfc
 def test_write_serial_(self, *_: Any) -> None:
     gateway = Gateway(operation=RX, local_test=False, dd_sockets=False, qubes=False)
     self.assertIsNone(gateway.write(b"message"))
コード例 #30
0
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)