Esempio n. 1
0
def get_onion_address_from_user(onion_address_user: str,
                                queues: 'QueueDict') -> str:
    """Get contact's Onion Address from user."""
    while True:
        onion_address_contact = box_input("Contact account",
                                          expected_len=ONION_ADDRESS_LENGTH)
        error_msg = validate_onion_addr(onion_address_contact,
                                        onion_address_user)

        if error_msg:
            m_print(error_msg, head=1)
            print_on_previous_line(reps=5, delay=1)

            if error_msg not in [
                    "Error: Invalid account length.",
                    "Error: Account must be in lower case.",
                    "Error: Can not add reserved account.",
                    "Error: Can not add own account."
            ]:
                relay_command = UNENCRYPTED_DATAGRAM_HEADER + UNENCRYPTED_ACCOUNT_CHECK + onion_address_contact.encode(
                )
                queue_to_nc(relay_command, queues[RELAY_PACKET_QUEUE])
            continue

        return onion_address_contact
Esempio n. 2
0
def decrypt_local_key(ts: 'datetime', packet: bytes, kdk_hashes: List[bytes],
                      packet_hashes: List[bytes], settings: 'Settings',
                      l_queue: 'local_key_queue') -> Tuple['datetime', bytes]:
    """Decrypt local key packet."""
    while True:
        kdk = get_b58_key(B58_LOCAL_KEY, settings)
        kdk_hash = blake2b(kdk)

        # Check if the key was an old one.
        if kdk_hash in kdk_hashes:
            m_print("Error: Entered an old local key decryption key.", delay=1)
            continue

        try:
            plaintext = auth_and_decrypt(packet, kdk)
        except nacl.exceptions.CryptoError:
            ts, plaintext = process_local_key_buffer(kdk, l_queue)

        protect_kdk(kdk)

        # Cache hashes needed to recognize reissued local key packets and key decryption keys.
        kdk_hashes.append(kdk_hash)
        packet_hashes.append(blake2b(packet))

        return ts, plaintext
Esempio n. 3
0
    def process_long_header(self,
                            packet:    bytes,
                            packet_ct: Optional[bytes] = None
                            ) -> None:
        """Process first packet of long transmission."""
        if self.long_active:
            self.add_masking_packet_to_log_file(increase=len(self.assembly_pt_list))

        if self.type == FILE:
            self.new_file_packet()
            try:
                lh, no_p_bytes, time_bytes, size_bytes, packet \
                    = separate_headers(packet, [ASSEMBLY_PACKET_HEADER_LENGTH] + 3*[ENCODED_INTEGER_LENGTH])

                self.packets = bytes_to_int(no_p_bytes)  # added by transmitter.packet.split_to_assembly_packets
                self.time    = str(timedelta(seconds=bytes_to_int(time_bytes)))
                self.size    = readable_size(bytes_to_int(size_bytes))
                self.name    = packet.split(US_BYTE)[0].decode()
                packet       = lh + packet

                m_print([f'Receiving file from {self.contact.nick}:',
                         f'{self.name} ({self.size})',
                         f'ETA {self.time} ({self.packets} packets)'], bold=True)

            except (struct.error, UnicodeError, ValueError):
                self.add_masking_packet_to_log_file()
                raise FunctionReturn("Error: Received file packet had an invalid header.")

        self.assembly_pt_list = [packet]
        self.long_active      = True
        self.is_complete      = False

        if packet_ct is not None:
            self.log_ct_list = [packet_ct]
Esempio n. 4
0
def contact_rem(onion_pub_key: bytes, ts: 'datetime',
                window_list: 'WindowList', contact_list: 'ContactList',
                group_list: 'GroupList', key_list: 'KeyList',
                settings: 'Settings', master_key: 'MasterKey') -> None:
    """Remove contact from Receiver Program."""
    key_list.remove_keyset(onion_pub_key)
    window_list.remove_window(onion_pub_key)
    short_addr = pub_key_to_short_address(onion_pub_key)

    try:
        contact = contact_list.get_contact_by_pub_key(onion_pub_key)
    except StopIteration:
        raise FunctionReturn(
            f"Receiver has no account '{short_addr}' to remove.")

    nick = contact.nick
    in_group = any([g.remove_members([onion_pub_key]) for g in group_list])

    contact_list.remove_contact_by_pub_key(onion_pub_key)

    message = f"Removed {nick} ({short_addr}) from contacts{' and groups' if in_group else ''}."
    m_print(message, bold=True, head=1, tail=1)

    local_win = window_list.get_local_window()
    local_win.add_new(ts, message)

    remove_logs(contact_list, group_list, settings, master_key, onion_pub_key)
Esempio n. 5
0
    def validate_traffic_masking_delay(key: str, value: 'SettingType',
                                       contact_list: 'ContactList') -> None:
        """Validate setting value for traffic masking delays."""
        if key in ["tm_static_delay", "tm_random_delay"]:

            for key_, name, min_setting in [
                ("tm_static_delay", "static",
                 TRAFFIC_MASKING_MIN_STATIC_DELAY),
                ("tm_random_delay", "random", TRAFFIC_MASKING_MIN_RANDOM_DELAY)
            ]:
                if key == key_ and value < min_setting:
                    raise SoftError(
                        f"Error: Can't set {name} delay lower than {min_setting}.",
                        head_clear=True)

            if contact_list.settings.software_operation == TX:
                m_print([
                    "WARNING!",
                    "Changing traffic masking delay can make your endpoint and traffic look unique!"
                ],
                        bold=True,
                        head=1,
                        tail=1)

                if not yes("Proceed anyway?"):
                    raise SoftError("Aborted traffic masking setting change.",
                                    head_clear=True)

            m_print("Traffic masking setting will change on restart.",
                    head=1,
                    tail=1)
Esempio n. 6
0
def send_onion_service_key(contact_list: 'ContactList', settings: 'Settings',
                           onion_service: 'OnionService',
                           gateway: 'Gateway') -> None:
    """Resend Onion Service key to Relay Program on Networked Computer.

    This command is used in cases where Relay Program had to be
    restarted for some reason (e.g. due to system updates).
    """
    try:
        if settings.traffic_masking:
            m_print([
                "Warning!",
                "Exporting Onion Service data to Networked Computer ",
                "during traffic masking can reveal to an adversary ",
                "TFC is being used at the moment. You should only do ",
                "this if you've had to restart the Relay Program."
            ],
                    bold=True,
                    head=1,
                    tail=1)
            if not yes("Proceed with the Onion Service data export?",
                       abort=False):
                raise SoftError("Onion Service data export canceled.",
                                tail_clear=True,
                                delay=1,
                                head=0)

        export_onion_service_data(contact_list, settings, onion_service,
                                  gateway)
    except (EOFError, KeyboardInterrupt):
        raise SoftError("Onion Service data export canceled.",
                        tail_clear=True,
                        delay=1,
                        head=2)
Esempio n. 7
0
def verify(window: 'TxWindow', contact_list: 'ContactList') -> None:
    """Verify fingerprints with contact."""
    if window.type == WIN_TYPE_GROUP or window.contact is None:
        raise SoftError("Error: A group is selected.", head_clear=True)

    if window.contact.uses_psk():
        raise SoftError("Pre-shared keys have no fingerprints.",
                        head_clear=True)

    try:
        verified = verify_fingerprints(window.contact.tx_fingerprint,
                                       window.contact.rx_fingerprint)
    except (EOFError, KeyboardInterrupt):
        raise SoftError("Fingerprint verification aborted.",
                        delay=1,
                        head=2,
                        tail_clear=True)

    status_hr, status = {
        True: ("Verified", KEX_STATUS_VERIFIED),
        False: ("Unverified", KEX_STATUS_UNVERIFIED)
    }[verified]

    window.contact.kex_status = status
    contact_list.store_contacts()
    m_print(f"Marked fingerprints with {window.name} as '{status_hr}'.",
            bold=True,
            tail_clear=True,
            delay=1,
            tail=1)
Esempio n. 8
0
File: crypto.py Progetto: gtog/tfc
def check_kernel_entropy() -> None:
    """Wait until the kernel CSPRNG is sufficiently seeded.

    Wait until the `entropy_avail` file states that kernel entropy pool
    has at least 512 bits of entropy. The waiting ensures the ChaCha20
    CSPRNG is fully seeded (i.e., it has the maximum of 384 bits of
    entropy) when it generates keys. The same entropy threshold is used
    by the GETRANDOM syscall in random.c:
        #define CRNG_INIT_CNT_THRESH (2*CHACHA20_KEY_SIZE)

    For more information on the kernel CSPRNG threshold, see
        https://security.stackexchange.com/a/175771/123524
        https://crypto.stackexchange.com/a/56377
    """
    message = "Waiting for kernel CSPRNG entropy pool to fill up"
    phase(message, head=1)

    ent_avail = 0
    while ent_avail < ENTROPY_THRESHOLD:
        with ignored(EOFError, KeyboardInterrupt):
            with open('/proc/sys/kernel/random/entropy_avail') as f:
                ent_avail = int(f.read().strip())
            m_print(f"{ent_avail}/{ENTROPY_THRESHOLD}")
            print_on_previous_line(delay=0.1)

    print_on_previous_line()
    phase(message)
    phase(DONE)
Esempio n. 9
0
def rxp_show_sys_win(
    user_input: 'UserInput',
    window: 'TxWindow',
    settings: 'Settings',
    queues: 'QueueDict',
) -> None:
    """\
    Display a system window on Receiver Program until the user presses
    Enter.

    Receiver Program has a dedicated window, WIN_UID_LOCAL, for system
    messages that shows information about received commands, status
    messages etc.

    Receiver Program also has another window, WIN_UID_FILE, that shows
    progress of file transmission from contacts that have traffic
    masking enabled.
    """
    cmd = user_input.plaintext.split()[0]
    win_uid = dict(cmd=WIN_UID_COMMAND, fw=WIN_UID_FILE)[cmd]

    command = WIN_SELECT + win_uid
    queue_command(command, settings, queues)

    try:
        m_print(f"<Enter> returns Receiver to {window.name}'s window",
                manual_proceed=True,
                box=True)
    except (EOFError, KeyboardInterrupt):
        pass

    print_on_previous_line(reps=4, flush=True)

    command = WIN_SELECT + window.uid
    queue_command(command, settings, queues)
Esempio n. 10
0
    def new_password(cls, purpose: str = "master password") -> str:
        """Prompt the user to enter and confirm a new password."""
        password_1 = pwd_prompt(f"Enter a new {purpose}: ")

        if password_1 == GENERATE:
            pwd_bit_strength, password_1 = MasterKey.generate_master_password()

            m_print([
                f"Generated a {pwd_bit_strength}-bit password:"******"Write down this password and dispose of the copy once you remember it.",
                "Press <Enter> to continue."
            ],
                    manual_proceed=True,
                    box=True,
                    head=1,
                    tail=1)
            reset_terminal()

            password_2 = password_1
        else:
            password_2 = pwd_prompt(f"Confirm the {purpose}: ", repeat=True)

        if password_1 == password_2:
            return password_1

        m_print("Error: Passwords did not match. Try again.", head=1, tail=1)
        print_on_previous_line(delay=1, reps=7)
        return cls.new_password(purpose)
Esempio n. 11
0
def show_value_diffs(value_type: str, true_value: str, purp_value: str,
                     local_test: bool) -> None:
    """Show differences between purported value and correct value."""
    # Pad with underscores to denote missing chars
    while len(purp_value) < ENCODED_B58_PUB_KEY_LENGTH:
        purp_value += '_'

    rep_arrows = ''
    purported = ''

    for c1, c2 in zip(purp_value, true_value):
        rep_arrows += ' ' if c1 == c2 else '↓'
        purported += c1

    message_list = [
        f"Source Computer received an invalid {value_type}.",
        "See arrows below that point to correct characters."
    ]

    if local_test:
        m_print(message_list + ['', purported, rep_arrows, true_value],
                box=True)
    else:
        purported = ' '.join(split_string(purported, item_len=7))
        rep_arrows = ' '.join(split_string(rep_arrows, item_len=7))
        true_value = ' '.join(split_string(true_value, item_len=7))

        m_print(message_list + [
            '', B58_PUBLIC_KEY_GUIDE, purported, rep_arrows, true_value,
            B58_PUBLIC_KEY_GUIDE
        ],
                box=True)
Esempio n. 12
0
    def __init__(
        self,
        message: str,
        window: Optional[
            'RxWindow'] = None,  # The window to include the message in
        output:
        bool = True,  # When False, doesn't print message when adding it to window
        bold: bool = False,  # When True, prints the message in bold
        head_clear:
        bool = False,  # When True, clears the screen before printing message
        tail_clear:
        bool = False,  # When True, clears the screen after message (needs delay)
        delay: float = 0,  # The delay before continuing
        head: int = 1,  # The number of new-lines to print before the message
        tail: int = 1,  # The number of new-lines to print after message
        ts: Optional['datetime'] = None  # Datetime object
    ) -> None:
        """Print return message and return to exception handler function."""
        self.message = message

        if window is None:
            if output:
                m_print(self.message,
                        bold=bold,
                        head_clear=head_clear,
                        tail_clear=tail_clear,
                        delay=delay,
                        head=head,
                        tail=tail)
        else:
            ts = datetime.now() if ts is None else ts
            window.add_new(ts, self.message, output=output)
Esempio n. 13
0
    def setup(self) -> None:
        """Prompt the user to enter initial serial interface setting.

        Ensure that the serial interface is available before proceeding.
        """
        if not self.local_testing_mode and not self.qubes:
            name = {
                TX: TRANSMITTER,
                NC: RELAY,
                RX: RECEIVER
            }[self.software_operation]

            self.use_serial_usb_adapter = yes(
                f"Use USB-to-serial/TTL adapter for {name} Computer?",
                head=1,
                tail=1)

            if self.use_serial_usb_adapter:
                for f in sorted(os.listdir('/dev/')):
                    if f.startswith('ttyUSB'):
                        return None
                m_print("Error: USB-to-serial/TTL adapter not found.")
                self.setup()
            else:
                if self.built_in_serial_interface not in sorted(
                        os.listdir('/dev/')):
                    m_print(
                        f"Error: Serial interface /dev/{self.built_in_serial_interface} not found."
                    )
                    self.setup()
Esempio n. 14
0
def rxp_load_psk(window:       'TxWindow',
                 contact_list: 'ContactList',
                 settings:     'Settings',
                 queues:       'QueueDict',
                 ) -> None:
    """Send command to Receiver Program to load PSK for active contact."""
    if settings.traffic_masking:
        raise SoftError("Error: Command is disabled during traffic masking.", head_clear=True)

    if window.type == WIN_TYPE_GROUP or window.contact is None:
        raise SoftError("Error: Group is selected.", head_clear=True)

    if not contact_list.get_contact_by_pub_key(window.uid).uses_psk():
        raise SoftError(f"Error: The current key was exchanged with {ECDHE}.", head_clear=True)

    c_code  = blake2b(window.uid, digest_size=CONFIRM_CODE_LENGTH)
    command = KEY_EX_PSK_RX + c_code + window.uid
    queue_command(command, settings, queues)

    while True:
        try:
            purp_code = ask_confirmation_code('Receiver')
            if purp_code == c_code.hex():
                window.contact.kex_status = KEX_STATUS_HAS_RX_PSK
                contact_list.store_contacts()
                raise SoftError(f"Removed PSK reminder for {window.name}.", tail_clear=True, delay=1)

            m_print("Incorrect confirmation code.", head=1)
            print_on_previous_line(reps=4, delay=2)

        except (EOFError, KeyboardInterrupt):
            raise SoftError("PSK install verification aborted.", tail_clear=True, delay=1, head=2)
Esempio n. 15
0
def verify_fingerprints(tx_fp: bytes,  # User's fingerprint
                        rx_fp: bytes   # Contact's fingerprint
                        ) -> bool:     # True if fingerprints match, else False
    """\
    Verify fingerprints over an authenticated out-of-band channel to
    detect MITM attacks against TFC's key exchange.

    MITM or man-in-the-middle attack is an attack against an inherent
    problem in cryptography:

    Cryptography is math, nothing more. During key exchange public keys
    are just very large numbers. There is no way to tell by looking if a
    number (received from an untrusted network / Networked Computer) is
    the same number the contact generated.

    Public key fingerprints are values designed to be compared by humans
    either visually or audibly (or sometimes by using semi-automatic
    means such as QR-codes). By comparing the fingerprint over an
    authenticated channel it's possible to verify that the correct key
    was received from the network.
    """
    m_print("To verify received public key was not replaced by an attacker, "
            "call the contact over an end-to-end encrypted line, preferably Signal "
            "(https://signal.org/). Make sure Signal's safety numbers have been "
            "verified, and then verbally compare the key fingerprints below.",
            head_clear=True, max_width=49, head=1, tail=1)

    print_fingerprint(tx_fp, "         Your fingerprint (you read)         ")
    print_fingerprint(rx_fp, "Purported fingerprint for contact (they read)")

    return yes("Is the contact's fingerprint correct?")
Esempio n. 16
0
def key_ex_psk_tx(packet: bytes, ts: 'datetime', window_list: 'WindowList',
                  contact_list: 'ContactList', key_list: 'KeyList',
                  settings: 'Settings') -> None:
    """Add contact and Tx-PSKs."""

    onion_pub_key, tx_mk, _, tx_hk, _, nick_bytes \
        = separate_headers(packet, [ONION_SERVICE_PUBLIC_KEY_LENGTH] + 4*[SYMMETRIC_KEY_LENGTH])

    try:
        nick = bytes_to_str(nick_bytes)
    except (struct.error, UnicodeError):
        raise SoftError("Error: Received invalid contact data")

    contact_list.add_contact(onion_pub_key, nick, bytes(FINGERPRINT_LENGTH),
                             bytes(FINGERPRINT_LENGTH), KEX_STATUS_NO_RX_PSK,
                             settings.log_messages_by_default,
                             settings.accept_files_by_default,
                             settings.show_notifications_by_default)

    # The Rx-side keys are set as null-byte strings to indicate they have not
    # been added yet. The zero-keys do not allow existential forgeries as
    # `decrypt_assembly_packet`does not allow the use of zero-keys for decryption.
    key_list.add_keyset(onion_pub_key=onion_pub_key,
                        tx_mk=tx_mk,
                        rx_mk=bytes(SYMMETRIC_KEY_LENGTH),
                        tx_hk=tx_hk,
                        rx_hk=bytes(SYMMETRIC_KEY_LENGTH))

    c_code = blake2b(onion_pub_key, digest_size=CONFIRM_CODE_LENGTH)
    message = f"Added Tx-side PSK for {nick} ({pub_key_to_short_address(onion_pub_key)})."
    cmd_win = window_list.get_command_window()
    cmd_win.add_new(ts, message)

    m_print([message, f"Confirmation code (to Transmitter): {c_code.hex()}"],
            box=True)
Esempio n. 17
0
def receiver_loop(queues:   Dict[bytes, 'Queue'],
                  gateway:  'Gateway',
                  unittest: bool = False
                  ) -> None:
    """Decode received packets and forward them to packet queues."""
    gateway_queue = queues[GATEWAY_QUEUE]

    while True:
        with ignored(EOFError, KeyboardInterrupt):
            if gateway_queue.qsize() == 0:
                time.sleep(0.01)

            _, packet = gateway_queue.get()

            try:
                packet = gateway.detect_errors(packet)
            except FunctionReturn:
                continue

            header, ts_bytes, payload = separate_headers(packet, [DATAGRAM_HEADER_LENGTH, DATAGRAM_TIMESTAMP_LENGTH])

            try:
                ts = datetime.strptime(str(bytes_to_int(ts_bytes)), "%Y%m%d%H%M%S%f")
            except (ValueError, struct.error):
                m_print("Error: Failed to decode timestamp in the received packet.", head=1, tail=1)
                continue

            if header in [MESSAGE_DATAGRAM_HEADER, FILE_DATAGRAM_HEADER,
                          COMMAND_DATAGRAM_HEADER, LOCAL_KEY_DATAGRAM_HEADER]:
                queues[header].put((ts, payload))

            if unittest:
                break
Esempio n. 18
0
def monitor_queues(tor: Tor, response: Any, queues: 'QueueDict') -> None:
    """Monitor queues for incoming packets."""
    while True:
        try:
            time.sleep(0.1)

            if queues[ONION_KEY_QUEUE].qsize() > 0:
                _, c_code = queues[ONION_KEY_QUEUE].get()

                m_print([
                    "Onion Service is already running.", '',
                    f"Onion Service confirmation code (to Transmitter): {c_code.hex()}"
                ],
                        box=True)

            if queues[ONION_CLOSE_QUEUE].qsize() > 0:
                command = queues[ONION_CLOSE_QUEUE].get()
                if not tor.platform_is_tails(
                ) and command == EXIT and tor.controller is not None:
                    tor.controller.remove_hidden_service(response.service_id)
                    tor.stop()
                queues[EXIT_QUEUE].put(command)
                time.sleep(5)
                break

        except (EOFError, KeyboardInterrupt):
            pass
        except stem.SocketClosed:
            if tor.controller is not None:
                tor.controller.remove_hidden_service(response.service_id)
                tor.stop()
            break
Esempio n. 19
0
def deliver_contact_data(
        header: bytes,  # Key type (x448, PSK)
        nick: str,  # Contact's nickname
        onion_pub_key: bytes,  # Public key of contact's v3 Onion Service
        tx_mk: bytes,  # Message key for outgoing messages
        rx_mk: bytes,  # Message key for incoming messages
        tx_hk: bytes,  # Header key for outgoing messages
        rx_hk: bytes,  # Header key for incoming messages
        queues: 'QueueDict',  # Dictionary of multiprocessing queues
        settings: 'Settings',  # Settings object
) -> None:
    """Deliver contact data to Destination Computer."""
    c_code = blake2b(onion_pub_key, digest_size=CONFIRM_CODE_LENGTH)
    command = (header + onion_pub_key + tx_mk + rx_mk + tx_hk + rx_hk +
               str_to_bytes(nick))

    queue_command(command, settings, queues)

    while True:
        purp_code = ask_confirmation_code("Receiver")
        if purp_code == c_code.hex():
            break

        elif purp_code == "":
            phase("Resending contact data", head=2)
            queue_command(command, settings, queues)
            phase(DONE)
            print_on_previous_line(reps=5)

        else:
            m_print("Incorrect confirmation code.", head=1)
            print_on_previous_line(reps=4, delay=2)
Esempio n. 20
0
def store_keys_on_removable_drive(
        ct_tag: bytes,  # Encrypted PSK
        salt: bytes,  # Salt for PSK decryption key derivation
        nick: str,  # Contact's nickname
        onion_pub_key: bytes,  # Public key of contact's v3 Onion Service
        onion_service: 'OnionService',  # OnionService object
        settings: 'Settings',  # Settings object
) -> None:
    """Store keys for contact on a removable media."""
    trunc_addr = pub_key_to_short_address(onion_pub_key)
    while True:
        store_d = ask_path_gui(f"Select removable media for {nick}", settings)
        f_name = f"{store_d}/{onion_service.user_short_address}.psk - Give to {trunc_addr}"

        try:
            with open(f_name, "wb+") as f:
                f.write(salt + ct_tag)
                f.flush()
                os.fsync(f.fileno())
            break
        except PermissionError:
            m_print(
                "Error: Did not have permission to write to the directory.",
                delay=0.5)
            continue
Esempio n. 21
0
def validate_contact_public_key(tfc_public_key_contact: bytes) -> None:
    """This function validates the public key from contact.

    The validation takes into account key state and it will detect if
    the public key is zero, but it can't predict whether the shared key
    will be zero. Further validation of the public key is done by the
    `src.common.crypto` module.
    """
    if len(tfc_public_key_contact) != TFC_PUBLIC_KEY_LENGTH:
        m_print([
            "Warning!", "Received invalid size public key.",
            "Aborting key exchange for your safety."
        ],
                bold=True,
                tail=1)
        raise SoftError("Error: Invalid public key length", output=False)

    if tfc_public_key_contact == bytes(TFC_PUBLIC_KEY_LENGTH):
        # The public key of contact is zero with negligible probability,
        # therefore we assume such key is malicious and attempts to set
        # the shared key to zero.
        m_print([
            "Warning!", "Received a malicious zero-public key.",
            "Aborting key exchange for your safety."
        ],
                bold=True,
                tail=1)
        raise SoftError("Error: Zero public key", output=False)
Esempio n. 22
0
def validate_contact_fingerprint(tx_fp: bytes, rx_fp: bytes) -> bytes:
    """Validate or skip validation of contact fingerprint.

    This function prompts the user to verify the fingerprint of the contact.
    If the user issues Ctrl+{C,D} command, this function will set the key
    exchange status as unverified.
    """
    try:
        if not verify_fingerprints(tx_fp, rx_fp):
            m_print([
                "Warning!", "Possible man-in-the-middle attack detected.",
                "Aborting key exchange for your safety."
            ],
                    bold=True,
                    tail=1)
            raise SoftError("Error: Fingerprint mismatch",
                            delay=2.5,
                            output=False)
        kex_status = KEX_STATUS_VERIFIED

    except (EOFError, KeyboardInterrupt):
        m_print([
            "Skipping fingerprint verification.", '', "Warning!",
            "Man-in-the-middle attacks can not be detected",
            "unless fingerprints are verified! To re-verify",
            "the contact, use the command '/verify'.", '',
            "Press <enter> to continue."
        ],
                manual_proceed=True,
                box=True,
                head=2,
                tail=1)
        kex_status = KEX_STATUS_UNVERIFIED

    return kex_status
Esempio n. 23
0
def deliver_onion_service_data(relay_command: bytes,
                               onion_service: 'OnionService',
                               gateway: 'Gateway') -> None:
    """Send Onion Service data to Replay Program on Networked Computer."""
    gateway.write(relay_command)
    while True:
        purp_code = ask_confirmation_code('Relay')

        if purp_code == onion_service.conf_code.hex():
            onion_service.is_delivered = True
            onion_service.new_confirmation_code()
            break

        if purp_code == '':
            phase("Resending Onion Service data", head=2)
            gateway.write(relay_command)
            phase(DONE)
            print_on_previous_line(reps=5)

        else:
            m_print([
                "Incorrect confirmation code. If Relay Program did not",
                "receive Onion Service data, resend it by pressing <Enter>."
            ],
                    head=1)
            print_on_previous_line(reps=5, delay=2)
Esempio n. 24
0
def deliver_local_key(local_key_packet: bytes, kek: bytes, c_code: bytes,
                      settings: 'Settings', queues: 'QueueDict') -> None:
    """Deliver encrypted local key to Destination Computer."""
    nc_bypass_msg(NC_BYPASS_START, settings)
    queue_to_nc(local_key_packet, queues[RELAY_PACKET_QUEUE])

    while True:
        print_key("Local key decryption key (to Receiver)", kek, settings)
        purp_code = ask_confirmation_code("Receiver")
        if purp_code == c_code.hex():
            nc_bypass_msg(NC_BYPASS_STOP, settings)
            break
        elif purp_code == "":
            phase("Resending local key", head=2)
            queue_to_nc(local_key_packet, queues[RELAY_PACKET_QUEUE])
            phase(DONE)
            print_on_previous_line(
                reps=(9 if settings.local_testing_mode else 10))
        else:
            m_print([
                "Incorrect confirmation code. If Receiver did not receive",
                "the encrypted local key, resend it by pressing <Enter>."
            ],
                    head=1)
            print_on_previous_line(
                reps=(9 if settings.local_testing_mode else 10), delay=2)
Esempio n. 25
0
def process_offset(
    offset: int,  # Number of dropped packets
    origin: bytes,  # "to/from" preposition
    direction: str,  # Direction of packet
    nick: str,  # Nickname of associated contact
    window: 'RxWindow'  # RxWindow object
) -> None:
    """Display warnings about increased offsets.

    If the offset has increased over the threshold, ask the user to
    confirm hash ratchet catch up.
    """
    if offset > HARAC_WARN_THRESHOLD and origin == ORIGIN_CONTACT_HEADER:
        m_print([
            f"Warning! {offset} packets from {nick} were not received.",
            f"This might indicate that {offset} most recent packets were ",
            "lost during transmission, or that the contact is attempting ",
            "a DoS attack. You can wait for TFC to attempt to decrypt the ",
            "packet, but it might take a very long time or even forever."
        ])

        if not yes("Proceed with the decryption?", abort=False, tail=1):
            raise SoftError(f"Dropped packet from {nick}.", window=window)

    elif offset:
        m_print(
            f"Warning! {offset} packet{'s' if offset > 1 else ''} {direction} {nick} were not received."
        )
Esempio n. 26
0
def key_ex_psk_tx(packet: bytes, ts: 'datetime', window_list: 'WindowList',
                  contact_list: 'ContactList', key_list: 'KeyList',
                  settings: 'Settings') -> None:
    """Add contact and Tx-PSKs."""

    onion_pub_key, tx_mk, _, tx_hk, _, nick_bytes \
        = separate_headers(packet, [ONION_SERVICE_PUBLIC_KEY_LENGTH] + 4*[SYMMETRIC_KEY_LENGTH])

    try:
        nick = bytes_to_str(nick_bytes)
    except (struct.error, UnicodeError):
        raise FunctionReturn("Error: Received invalid contact data")

    contact_list.add_contact(onion_pub_key, nick, bytes(FINGERPRINT_LENGTH),
                             bytes(FINGERPRINT_LENGTH), KEX_STATUS_NO_RX_PSK,
                             settings.log_messages_by_default,
                             settings.accept_files_by_default,
                             settings.show_notifications_by_default)

    # The Rx-side keys are set as null-byte strings to indicate they have not
    # been added yet. The zero-keys do not allow existential forgeries as
    # `decrypt_assembly_packet`does not allow the use of zero-keys for decryption.
    key_list.add_keyset(onion_pub_key=onion_pub_key,
                        tx_mk=tx_mk,
                        rx_mk=bytes(SYMMETRIC_KEY_LENGTH),
                        tx_hk=tx_hk,
                        rx_hk=bytes(SYMMETRIC_KEY_LENGTH))

    message = f"Added Tx-side PSK for {nick} ({pub_key_to_short_address(onion_pub_key)})."
    local_win = window_list.get_local_window()
    local_win.add_new(ts, message)

    m_print(message, bold=True, tail_clear=True, delay=1)
Esempio n. 27
0
def key_ex_ecdhe(packet: bytes, ts: 'datetime', window_list: 'WindowList',
                 contact_list: 'ContactList', key_list: 'KeyList',
                 settings: 'Settings') -> None:
    """Add contact and symmetric keys derived from X448 shared key."""

    onion_pub_key, tx_mk, rx_mk, tx_hk, rx_hk, nick_bytes \
        = separate_headers(packet, [ONION_SERVICE_PUBLIC_KEY_LENGTH] + 4*[SYMMETRIC_KEY_LENGTH])

    try:
        nick = bytes_to_str(nick_bytes)
    except (struct.error, UnicodeError):
        raise FunctionReturn("Error: Received invalid contact data")

    contact_list.add_contact(onion_pub_key, nick, bytes(FINGERPRINT_LENGTH),
                             bytes(FINGERPRINT_LENGTH), KEX_STATUS_NONE,
                             settings.log_messages_by_default,
                             settings.accept_files_by_default,
                             settings.show_notifications_by_default)

    key_list.add_keyset(onion_pub_key, tx_mk, rx_mk, tx_hk, rx_hk)

    message = f"Successfully added {nick}."
    local_win = window_list.get_local_window()
    local_win.add_new(ts, message)

    c_code = blake2b(onion_pub_key, digest_size=CONFIRM_CODE_LENGTH)
    m_print([message, f"Confirmation code (to Transmitter): {c_code.hex()}"],
            box=True)
Esempio n. 28
0
 def check_key_in_key_list(self, key: str, json_dict: Any) -> None:
     """Check if the setting's key value is in the setting dictionary."""
     if key not in json_dict:
         m_print([f"Error: Missing setting '{key}' in '{self.file_name}'.",
                  f"The value has been set to default ({self.defaults[key]})."], head=1, tail=1)
         setattr(self, key, self.defaults[key])
         raise SoftError("Missing key", output=False)
Esempio n. 29
0
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_MGR_QUEUE].put(enabled)
Esempio n. 30
0
 def invalid_setting(self,
                     key:       str,
                     json_dict: Dict[str, Union[bool, int, str]]
                     ) -> None:
     """Notify about setting an invalid value to default value."""
     m_print([f"Error: Invalid value '{json_dict[key]}' for setting '{key}' in '{self.file_name}'.",
              f"The value has been set to default ({self.defaults[key]})."], head=1, tail=1)
     setattr(self, key, self.defaults[key])