Пример #1
0
def clear_screens(user_input: 'UserInput', window: 'TxWindow',
                  settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None:
    """Clear/reset TxM, RxM and NH screens.

    Only send unencrypted command to NH if traffic masking is disabled and
    if some related IM account can be bound to active window.

    Since reset command removes ephemeral message log on RxM, TxM decides
    the window to reset (in case e.g. previous window selection command
    packet dropped and active window state is inconsistent between TxM/RxM).
    """
    cmd = user_input.plaintext.split()[0]

    command = CLEAR_SCREEN_HEADER if cmd == CLEAR else RESET_SCREEN_HEADER + window.uid.encode(
    )
    queue_command(command, settings, queues[COMMAND_PACKET_QUEUE])

    clear_screen()

    if not settings.session_traffic_masking and window.imc_name is not None:
        im_window = window.imc_name.encode()
        pt_cmd = UNENCRYPTED_SCREEN_CLEAR if cmd == CLEAR else UNENCRYPTED_SCREEN_RESET
        packet = UNENCRYPTED_PACKET_HEADER + pt_cmd + im_window
        queue_to_nh(packet, settings, queues[NH_PACKET_QUEUE])

    if cmd == RESET:
        os.system('reset')
Пример #2
0
    def select_tx_window(
        self,
        settings: 'Settings',  # Settings object
        queues: 'QueueDict',  # Dictionary of Queues
        onion_service: 'OnionService',  # OnionService object
        gateway: 'Gateway',  # Gateway object
        selection: Optional[str] = None,  # Selector for window
        cmd: bool = False  # True when `/msg` command is used to switch window
    ) -> None:
        """Select specified window or ask the user to specify one."""
        if selection is None:
            selection = self.select_recipient()

        if selection in self.group_list.get_list_of_group_names():
            self.select_group(selection, cmd, settings)

        elif selection in self.contact_list.contact_selectors():
            self.select_contact(selection, cmd, queues, settings)

        elif selection.startswith("/"):
            self.window_selection_command(selection, settings, queues,
                                          onion_service, gateway)

        else:
            raise SoftError("Error: No contact/group was found.")

        if settings.traffic_masking:
            queues[WINDOW_SELECT_QUEUE].put(self.window_contacts)

        packet = WIN_SELECT + self.uid
        queue_command(packet, settings, queues)

        clear_screen()
Пример #3
0
    def load_master_key(self) -> bytes:
        """Derive the master key from password and salt.

        Load the salt, hash, and key derivation settings from the login
        database. Derive the purported master key from the salt and
        entered password. If the BLAKE2b hash of derived master key
        matches the hash in the login database, accept the derived
        master key.
        """
        database_data = self.database.load_database()

        if len(database_data) != MASTERKEY_DB_SIZE:
            raise CriticalError(f"Invalid {self.file_name} database size.")

        salt, key_hash, time_bytes, memory_bytes, parallelism_bytes \
            = separate_headers(database_data, [ARGON2_SALT_LENGTH, BLAKE2_DIGEST_LENGTH,
                                               ENCODED_INTEGER_LENGTH, ENCODED_INTEGER_LENGTH])

        time_cost   = bytes_to_int(time_bytes)
        memory_cost = bytes_to_int(memory_bytes)
        parallelism = bytes_to_int(parallelism_bytes)

        while True:
            password = MasterKey.get_password()
            phase("Deriving master key", head=2, offset=len("Password correct"))
            purp_key = argon2_kdf(password, salt, time_cost, memory_cost, parallelism)

            if blake2b(purp_key) == key_hash:
                phase("Password correct", done=True, delay=1)
                clear_screen()
                return purp_key

            phase("Invalid password", done=True, delay=1)
            print_on_previous_line(reps=5)
Пример #4
0
def change_master_key(ts: 'datetime', window_list: 'WindowList',
                      contact_list: 'ContactList', group_list: 'GroupList',
                      key_list: 'KeyList', settings: 'Settings',
                      master_key: 'MasterKey') -> None:
    """Prompt user for new master password and derive new master key from that."""
    try:
        old_master_key = master_key.master_key[:]
        master_key.new_master_key()

        phase("Re-encrypting databases")

        ensure_dir(DIR_USER_DATA)
        file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
        if os.path.isfile(file_name):
            re_encrypt(old_master_key, master_key.master_key, settings)

        key_list.store_keys()
        settings.store_settings()
        contact_list.store_contacts()
        group_list.store_groups()

        phase(DONE)
        box_print("Master key successfully changed.", head=1)
        clear_screen(delay=1.5)

        local_win = window_list.get_window(LOCAL_ID)
        local_win.add_new(ts, "Changed RxM master key.")

    except KeyboardInterrupt:
        raise FunctionReturn("Password change aborted.",
                             delay=1,
                             head=3,
                             tail_clear=True)
Пример #5
0
def add_psk_tx_keys(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList',
                    contact_list: 'ContactList', key_list: 'KeyList',
                    settings: 'Settings', pubkey_buf: Dict[str,
                                                           bytes]) -> None:
    """Add contact and Tx-PSKs."""
    tx_key = cmd_data[0:32]
    tx_hek = cmd_data[32:64]

    account, nick = [f.decode() for f in cmd_data[64:].split(US_BYTE)]

    contact_list.add_contact(account, DUMMY_USER, nick, bytes(FINGERPRINT_LEN),
                             bytes(FINGERPRINT_LEN),
                             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. This does not allow existential forgeries as
    # decrypt_assembly_packet does not allow use of zero-keys for decryption.
    key_list.add_keyset(account,
                        tx_key=tx_key,
                        rx_key=bytes(KEY_LENGTH),
                        tx_hek=tx_hek,
                        rx_hek=bytes(KEY_LENGTH))

    pubkey_buf.pop(account, None)

    message = f"Added Tx-PSK for {nick} ({account})."
    local_win = window_list.get_window(LOCAL_ID)
    local_win.add_new(ts, message)

    box_print(message)
    clear_screen(delay=1)
Пример #6
0
def add_x25519_keys(packet: bytes, ts: 'datetime', window_list: 'WindowList',
                    contact_list: 'ContactList', key_list: 'KeyList',
                    settings: 'Settings', pubkey_buf: Dict[str,
                                                           bytes]) -> None:
    """Add contact and their X25519 keys."""
    tx_key = packet[0:32]
    tx_hek = packet[32:64]
    rx_key = packet[64:96]
    rx_hek = packet[96:128]

    account, nick = [f.decode() for f in packet[128:].split(US_BYTE)]

    contact_list.add_contact(account, DUMMY_USER, nick, bytes(FINGERPRINT_LEN),
                             bytes(FINGERPRINT_LEN),
                             settings.log_messages_by_default,
                             settings.accept_files_by_default,
                             settings.show_notifications_by_default)

    key_list.add_keyset(account, tx_key, rx_key, tx_hek, rx_hek)

    pubkey_buf.pop(account, None)

    message = f"Added X25519 keys for {nick} ({account})."
    local_win = window_list.get_window(LOCAL_ID)
    local_win.add_new(ts, message)

    box_print(message)
    clear_screen(delay=1)
Пример #7
0
def print_about() -> None:
    """Print URLs that direct to TFC's project site and documentation."""
    clear_screen()
    print(f"\n Tinfoil Chat {VERSION}\n\n"
          " Website:     https://github.com/maqp/tfc/\n"
          " Wikipage:    https://github.com/maqp/tfc/wiki\n"
          " White paper: https://cs.helsinki.fi/u/oottela/tfc.pdf\n")
Пример #8
0
    def print_contacts(self) -> None:
        """Print list of contacts."""
        # Columns
        c1 = ['Contact']
        c2 = ['Logging']
        c3 = ['Notify']
        c4 = ['Files ']
        c5 = ['Key Ex']
        c6 = ['Account']

        for c in self.get_list_of_contacts():
            c1.append(c.nick)
            c2.append('Yes'    if c.log_messages                             else 'No')
            c3.append('Yes'    if c.notifications                            else 'No')
            c4.append('Accept' if c.file_reception                           else 'Reject')
            c5.append('PSK'    if c.tx_fingerprint == bytes(FINGERPRINT_LEN) else 'X25519')
            c6.append(c.rx_account)

        lst = []
        for nick, log_setting, notify_setting, file_rcv_setting, key_exchange, account in zip(c1, c2, c3, c4, c5, c6):
            lst.append('{0:{1}} {2:{3}} {4:{5}} {6:{7}} {8:{9}} {10}'.format(
                nick,             max(len(v) for v in c1) + CONTACT_LIST_INDENT,
                log_setting,      max(len(v) for v in c2) + CONTACT_LIST_INDENT,
                notify_setting,   max(len(v) for v in c3) + CONTACT_LIST_INDENT,
                file_rcv_setting, max(len(v) for v in c4) + CONTACT_LIST_INDENT,
                key_exchange,     max(len(v) for v in c5) + CONTACT_LIST_INDENT,
                account,          max(len(v) for v in c6) + CONTACT_LIST_INDENT))

        lst.insert(1, get_terminal_width() * '─')
        clear_screen()
        print('\n' + '\n'.join(lst) + '\n\n')
Пример #9
0
def process_public_key(ts: 'datetime', packet: bytes,
                       window_list: 'WindowList', settings: 'Settings',
                       pubkey_buf: Dict[str, bytes]) -> None:
    """Display contact's public key and add it to buffer."""
    pub_key = packet[1:33]
    origin = packet[33:34]

    try:
        account = packet[34:].decode()
    except UnicodeError:
        raise FunctionReturn(
            "Error! Account for received public key had invalid encoding.")

    if origin not in [ORIGIN_CONTACT_HEADER, ORIGIN_USER_HEADER]:
        raise FunctionReturn(
            "Error! Received public key had an invalid origin header.")

    if origin == ORIGIN_CONTACT_HEADER:
        pubkey_buf[account] = pub_key
        print_key(f"Received public key from {account}:", pub_key, settings)

        local_win = window_list.get_local_window()
        pub_key_b58 = ' '.join(
            split_string(b58encode(pub_key),
                         item_len=(51 if settings.local_testing_mode else 3)))
        local_win.add_new(
            ts, f"Received public key from {account}: {pub_key_b58}")

    elif origin == ORIGIN_USER_HEADER and account in pubkey_buf:
        clear_screen()
        print_key(f"Public key for {account}:", pubkey_buf[account], settings)
Пример #10
0
def get_b58_key(key_type:      str,         # The type of Base58 key to be entered
                settings:      'Settings',  # Settings object
                short_address: str = ''     # The contact's short Onion address
                ) -> bytes:                 # The Base58 decoded key
    """Ask the user to input a Base58 encoded key."""
    if key_type == B58_PUBLIC_KEY:
        clear_screen()
        m_print(f"{ECDHE} key exchange", head=1, tail=1, bold=True)
        m_print("If needed, resend your public key to the contact by pressing <Enter>", tail=1)

        box_msg = f"Enter public key of {short_address} (from Relay)"
    elif key_type == B58_LOCAL_KEY:
        box_msg = "Enter local key decryption key (from Transmitter)"
    else:
        raise CriticalError("Invalid key type")

    while True:
        rx_pk = box_input(box_msg, key_type=key_type, guide=not (settings.local_testing_mode or settings.qubes))
        rx_pk = ''.join(rx_pk.split())

        if key_type == B58_PUBLIC_KEY and rx_pk == '':
            return rx_pk.encode()

        try:
            return b58decode(rx_pk, public_key=(key_type == B58_PUBLIC_KEY))
        except ValueError:
            m_print("Checksum error - Check that the entered key is correct.")
            print_on_previous_line(reps=(4 if settings.local_testing_mode else 5), delay=1)

            if key_type == B58_PUBLIC_KEY and len(rx_pk) == ENCODED_B58_PUB_KEY_LENGTH:
                raise ValueError(rx_pk)
Пример #11
0
def print_logs(message_list: List[MsgTuple],
               export:       bool,
               msg_to_load:  int,
               window:       Union['TxWindow', 'RxWindow'],
               contact_list: 'ContactList',
               group_list:   'GroupList',
               settings:     'Settings'
               ) -> None:
    """Print list of logged messages to screen or export them to file."""
    terminal_width = get_terminal_width()
    system, m_dir  = {TX: ("Transmitter", "sent to"),
                      RX: ("Receiver",    "to/from")}[settings.software_operation]

    f_name = open(f"{system} - Plaintext log ({window.name})", 'w+') if export else sys.stdout
    subset = '' if msg_to_load == 0 else f"{msg_to_load} most recent "
    title  = textwrap.fill(f"Log file of {subset}message(s) {m_dir} {window.type} {window.name}", terminal_width)

    packet_list            = PacketList(settings, contact_list)
    log_window             = RxWindow(window.uid, contact_list, group_list, settings, packet_list)
    log_window.is_active   = True
    log_window.message_log = message_list

    if message_list:
        if not export:
            clear_screen()
        print(title,                 file=f_name)
        print(terminal_width * '═',  file=f_name)
        log_window.redraw(           file=f_name)
        print("<End of log file>\n", file=f_name)
    else:
        raise FunctionReturn(f"No logged messages for {window.type} '{window.name}'.", head_clear=True)

    if export:
        f_name.close()
Пример #12
0
def ensure_im_connection() -> None:
    """\
    Check that nh.py has connection to Pidgin
    before launching other processes.
    """
    phase("Waiting for enabled account in Pidgin", offset=1)

    while True:
        try:
            bus = dbus.SessionBus(private=True)
            obj = bus.get_object("im.pidgin.purple.PurpleService",
                                 "/im/pidgin/purple/PurpleObject")
            purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")

            while not purple.PurpleAccountsGetAllActive():
                time.sleep(0.01)
            phase('OK', done=True)

            accounts = []
            for a in purple.PurpleAccountsGetAllActive():
                accounts.append(purple.PurpleAccountGetUsername(a)[:-1])

            just_len = len(max(accounts, key=len))
            justified = ["Active accounts in Pidgin:"] + [
                "* {}".format(a.ljust(just_len)) for a in accounts
            ]
            box_print(justified, head=1, tail=1)
            return None

        except (IndexError, dbus.exceptions.DBusException):
            continue
        except (EOFError, KeyboardInterrupt):
            clear_screen()
            exit()
Пример #13
0
def clear_screens(user_input: 'UserInput', window: 'TxWindow',
                  settings: 'Settings', queues: 'QueueDict') -> None:
    """Clear/reset screen of Source, Destination, and Networked Computer.

    Only send an unencrypted command to Networked Computer if traffic
    masking is disabled.

    With clear command, sending only the command header is enough.
    However, as reset command removes the ephemeral message log on
    Receiver Program, Transmitter Program must define the window to
    reset (in case, e.g., previous window selection command packet
    dropped, and active window state is inconsistent between the
    TCB programs).
    """
    clear = user_input.plaintext.split()[0] == CLEAR

    command = CLEAR_SCREEN if clear else RESET_SCREEN + window.uid
    queue_command(command, settings, queues)

    clear_screen()

    if not settings.traffic_masking:
        pt_cmd = UNENCRYPTED_SCREEN_CLEAR if clear else UNENCRYPTED_SCREEN_RESET
        packet = UNENCRYPTED_DATAGRAM_HEADER + pt_cmd
        queue_to_nc(packet, queues[RELAY_PACKET_QUEUE])

    if not clear:
        readline.clear_history()
        reset_terminal()
Пример #14
0
def process_arguments() -> Tuple[str, int, int]:
    """Load simulator settings from the command line argument."""
    try:
        argv = str(sys.argv[1])
        input_socket, output_socket = {
            SCNCLR: (SRC_DD_LISTEN_SOCKET, RP_LISTEN_SOCKET),
            SCNCRL: (SRC_DD_LISTEN_SOCKET, RP_LISTEN_SOCKET),
            NCDCLR: (DST_DD_LISTEN_SOCKET, DST_LISTEN_SOCKET),
            NCDCRL: (DST_DD_LISTEN_SOCKET, DST_LISTEN_SOCKET)
        }[argv]

        return argv, input_socket, output_socket

    except (IndexError, KeyError):
        clear_screen()
        print(
            f"\nUsage: python3.7 dd.py [OPTION]\n\n"
            f"\nMandatory arguments"
            f"\n Argument  Simulate data diode between..."
            f"\n   {SCNCLR}    Source Computer    and Networked Computer   (left to right)"
            f"\n   {SCNCRL}    Source Computer    and Networked Computer   (right to left)"
            f"\n   {NCDCLR}    Networked Computer and Destination Computer (left to right)"
            f"\n   {NCDCRL}    Networked Computer and Destination Computer (right to left)"
        )
        sys.exit(1)
Пример #15
0
def wipe(settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None:
    """Reset terminals, wipe all user data from TxM/RxM/NH and power off systems.

    No effective RAM overwriting tool currently exists, so as long as TxM/RxM
    use FDE and DDR3 memory, recovery of user data becomes impossible very fast:

        https://www1.cs.fau.de/filepool/projects/coldboot/fares_coldboot.pdf
    """
    if not yes("Wipe all user data and power off systems?"):
        raise FunctionReturn("Wipe command aborted.")

    clear_screen()

    for q in [COMMAND_PACKET_QUEUE, NH_PACKET_QUEUE]:
        while queues[q].qsize() != 0:
            queues[q].get()

    queue_command(WIPE_USER_DATA_HEADER, settings,
                  queues[COMMAND_PACKET_QUEUE])

    if not settings.session_traffic_masking:
        if settings.local_testing_mode:
            time.sleep(0.8)
            if settings.data_diode_sockets:
                time.sleep(2.2)
        else:
            time.sleep(settings.race_condition_delay)

    queue_to_nh(UNENCRYPTED_PACKET_HEADER + UNENCRYPTED_WIPE_COMMAND, settings,
                queues[NH_PACKET_QUEUE])

    os.system('reset')
Пример #16
0
Файл: dd.py Проект: AJMartel/tfc
def animate(argv: str) -> None:
    """Animate the data diode."""
    animation_length = 16
    for i in range(animation_length):
        clear_screen()
        draw_frame(argv, 'Data flow', high=(i % 2 == 0))
        time.sleep(0.04)
    clear_screen()
    draw_frame(argv, 'Idle', high=False)
Пример #17
0
def animate(argv: str) -> None:
    """Animate the data diode transmission indicator."""
    for i in range(DD_ANIMATION_LENGTH):
        clear_screen()
        draw_frame(argv, DATA_FLOW, high=(i % 2 == 0))
        time.sleep(0.04)

    clear_screen()
    draw_frame(argv, IDLE)
Пример #18
0
 def setup(self) -> None:
     """Prompt user to enter initial settings."""
     clear_screen()
     if not self.local_testing_mode:
         if self.software_operation == TX:
             self.txm_usb_serial_adapter = yes(
                 "Does TxM use USB-to-serial/TTL adapter?", head=1, tail=1)
         else:
             self.rxm_usb_serial_adapter = yes(
                 "Does RxM use USB-to-serial/TTL adapter?", head=1, tail=1)
Пример #19
0
def graceful_exit(message: str = '',
                  clear: bool = True,
                  exit_code: int = 0) -> None:
    """Display a message and exit TFC."""
    if clear:
        clear_screen()
    if message:
        print('\n' + message)
    print("\nExiting TFC.\n")

    sys.exit(exit_code)
Пример #20
0
    def print_contacts(self) -> None:
        """Print the list of contacts.

        Neatly printed contact list allows easy contact management:
        It allows the user to check active logging, file reception and
        notification settings, as well as what key exchange was used
        and what is the state of that key exchange. The contact list
        also shows and what the account displayed by the Relay Program
        corresponds to what nick etc.
        """
        # Initialize columns
        c1 = ['Contact']
        c2 = ['Account']
        c3 = ['Logging']
        c4 = ['Notify']
        c5 = ['Files ']
        c6 = ['Key Ex']

        # Key exchange status dictionary
        kex_dict = {
            KEX_STATUS_PENDING: f"{ECDHE} (Pending)",
            KEX_STATUS_UNVERIFIED: f"{ECDHE} (Unverified)",
            KEX_STATUS_VERIFIED: f"{ECDHE} (Verified)",
            KEX_STATUS_NO_RX_PSK: f"{PSK}  (No contact key)",
            KEX_STATUS_HAS_RX_PSK: PSK
        }

        # Populate columns with contact data
        for c in self.get_list_of_contacts():
            c1.append(c.nick)
            c2.append(c.short_address)
            c3.append('Yes' if c.log_messages else 'No')
            c4.append('Yes' if c.notifications else 'No')
            c5.append('Accept' if c.file_reception else 'Reject')
            c6.append(kex_dict[c.kex_status])

        # Calculate column widths
        c1w, c2w, c3w, c4w, c5w, = [
            max(len(v) for v in column) + CONTACT_LIST_INDENT
            for column in [c1, c2, c3, c4, c5]
        ]

        # Align columns by adding whitespace between fields of each line
        lines = [
            f'{f1:{c1w}}{f2:{c2w}}{f3:{c3w}}{f4:{c4w}}{f5:{c5w}}{f6}'
            for f1, f2, f3, f4, f5, f6 in zip(c1, c2, c3, c4, c5, c6)
        ]

        # Add a terminal-wide line between the column names and the data
        lines.insert(1, get_terminal_width() * '─')

        # Print the contact list
        clear_screen()
        print('\n' + '\n'.join(lines) + '\n\n')
Пример #21
0
def graceful_exit(message:   str  = '',    # Exit message to print
                  clear:     bool = True,  # When False, does not clear screen before printing message
                  exit_code: int  = 0      # Value returned to parent process
                  ) -> None:
    """Display a message and exit TFC."""
    if clear:
        clear_screen()
    if message:
        print('\n' + message)
    print(f"\nExiting {TFC}.\n")

    sys.exit(exit_code)
Пример #22
0
def local_key_installed(ts: 'datetime', window_list: 'WindowList',
                        contact_list: 'ContactList') -> None:
    """Clear local key bootstrap process from screen."""
    message = "Successfully completed local key exchange."
    local_win = window_list.get_window(LOCAL_ID)
    local_win.add_new(ts, message)

    box_print(message)
    clear_screen(delay=1)

    if not contact_list.has_contacts():
        c_print("Waiting for new contacts", head=1, tail=1)
Пример #23
0
    def select_tx_window(self,
                         settings:  'Settings',
                         queues:    Dict[bytes, 'Queue'],
                         selection: str  = None,
                         cmd:       bool = False) -> None:
        """Select specified window or ask the user to specify one."""
        if selection is None:
            self.contact_list.print_contacts()
            self.group_list.print_groups()
            selection = input("Select recipient: ").strip()

        if selection in self.group_list.get_list_of_group_names():
            if cmd and settings.session_traffic_masking and selection != self.uid:
                raise FunctionReturn("Error: Can't change window during traffic masking.")

            self.group           = self.group_list.get_group(selection)
            self.window_contacts = self.group.members
            self.name            = self.group.name
            self.uid             = self.name
            self.log_messages    = self.group.log_messages
            self.type            = WIN_TYPE_GROUP
            self.type_print      = 'group'

            if self.window_contacts:
                self.imc_name = self.window_contacts[0].rx_account

        elif selection in self.contact_list.contact_selectors():
            if cmd and settings.session_traffic_masking:
                contact = self.contact_list.get_contact(selection)
                if contact.rx_account != self.uid:
                    raise FunctionReturn("Error: Can't change window during traffic masking.")

            self.contact         = self.contact_list.get_contact(selection)
            self.window_contacts = [self.contact]
            self.name            = self.contact.nick
            self.uid             = self.contact.rx_account
            self.imc_name        = self.contact.rx_account
            self.log_messages    = self.contact.log_messages
            self.type            = WIN_TYPE_CONTACT
            self.type_print      = 'contact'

        else:
            raise FunctionReturn("Error: No contact/group was found.")

        if settings.session_traffic_masking and not cmd:
            queues[WINDOW_SELECT_QUEUE].put((self.window_contacts, self.log_messages))

        packet = WINDOW_SELECT_HEADER + self.uid.encode()
        queue_command(packet, settings, queues[COMMAND_PACKET_QUEUE])

        clear_screen()
Пример #24
0
def process_local_key(ts: 'datetime', packet: bytes, window_list: 'WindowList',
                      contact_list: 'ContactList', key_list: 'KeyList',
                      settings: 'Settings') -> None:
    """Decrypt local key packet and add local contact/keyset."""
    bootstrap = not key_list.has_local_key()

    try:
        while True:
            clear_screen()
            box_print("Received encrypted local key", tail=1)
            kdk = get_b58_key(B58_LOCAL_KEY, settings)

            try:
                pt = auth_and_decrypt(packet[1:], key=kdk, soft_e=True)
                break
            except nacl.exceptions.CryptoError:
                if bootstrap:
                    raise FunctionReturn(
                        "Error: Incorrect key decryption key.", delay=1.5)
                c_print("Incorrect key decryption key.", head=1)
                clear_screen(delay=1.5)

        key = pt[0:32]
        hek = pt[32:64]
        conf_code = pt[64:65]

        # Add local contact to contact list database
        contact_list.add_contact(LOCAL_ID, LOCAL_ID, LOCAL_ID,
                                 bytes(FINGERPRINT_LEN),
                                 bytes(FINGERPRINT_LEN), False, False, True)

        # Add local keyset to keyset database
        key_list.add_keyset(rx_account=LOCAL_ID,
                            tx_key=key,
                            rx_key=csprng(),
                            tx_hek=hek,
                            rx_hek=csprng())

        box_print(f"Confirmation code for TxM: {conf_code.hex()}", head=1)

        local_win = window_list.get_local_window()
        local_win.add_new(ts, "Added new local key.")

        if bootstrap:
            window_list.active_win = local_win

    except KeyboardInterrupt:
        raise FunctionReturn("Local key setup aborted.",
                             delay=1,
                             head=3,
                             tail_clear=True)
Пример #25
0
def change_master_key(user_input: 'UserInput', contact_list: 'ContactList',
                      group_list: 'GroupList', settings: 'Settings',
                      queues: Dict[bytes,
                                   'Queue'], master_key: 'MasterKey') -> None:
    """Change master key on TxM/RxM."""
    try:
        if settings.session_traffic_masking:
            raise FunctionReturn(
                "Error: Command is disabled during traffic masking.")

        try:
            device = user_input.plaintext.split()[1].lower()
        except IndexError:
            raise FunctionReturn("Error: No target system specified.")

        if device not in [TX, RX]:
            raise FunctionReturn("Error: Invalid target system.")

        if device == RX:
            queue_command(CHANGE_MASTER_K_HEADER, settings,
                          queues[COMMAND_PACKET_QUEUE])
            return None

        old_master_key = master_key.master_key[:]
        master_key.new_master_key()
        new_master_key = master_key.master_key

        phase("Re-encrypting databases")

        queues[KEY_MANAGEMENT_QUEUE].put(
            (KDB_CHANGE_MASTER_KEY_HEADER, master_key))

        ensure_dir(DIR_USER_DATA)
        file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
        if os.path.isfile(file_name):
            re_encrypt(old_master_key, new_master_key, settings)

        settings.store_settings()
        contact_list.store_contacts()
        group_list.store_groups()

        phase(DONE)
        box_print("Master key successfully changed.", head=1)
        clear_screen(delay=1.5)

    except KeyboardInterrupt:
        raise FunctionReturn("Password change aborted.",
                             delay=1,
                             head=3,
                             tail_clear=True)
Пример #26
0
    def redraw(self, file=None) -> None:
        """Print all messages received to window."""
        self.unread_messages = 0

        if file is None:
            clear_screen()

        if self.message_log:
            self.previous_msg_ts = self.message_log[0][0]
            self.create_handle_dict(self.message_log)
            for msg_tuple in self.message_log:
                self.print(msg_tuple, file)
        else:
            c_print(f"This window for {self.name} is currently empty.",
                    head=1,
                    tail=1)
Пример #27
0
def get_b58_key(key_type: str, settings: 'Settings') -> bytes:
    """Ask user to input Base58 encoded public key from RxM.

    For file keys, use testnet address format instead to
    prevent file injected via import from accidentally
    being decrypted with public key from adversary.
    """
    if key_type == B58_PUB_KEY:
        clear_screen()
        c_print("Import public key from RxM", head=1, tail=1)
        c_print("WARNING")
        message_printer(
            "Outside specific requests TxM (this computer) "
            "makes, you must never copy any data from "
            "NH/RxM to TxM. Doing so could infect TxM, that "
            "could then later covertly transmit private "
            "keys/messages to attacker.",
            head=1,
            tail=1)
        message_printer("You can resend your public key by typing 'resend'",
                        tail=1)
        box_msg = "Enter contact's public key from RxM"
    elif key_type == B58_LOCAL_KEY:
        box_msg = "Enter local key decryption key from TxM"
    elif key_type == B58_FILE_KEY:
        box_msg = "Enter file decryption key"
    else:
        raise CriticalError("Invalid key type")

    while True:
        if settings.local_testing_mode or key_type == B58_FILE_KEY:
            pub_key = box_input(box_msg, expected_len=51)
            small = True
        else:
            pub_key = box_input(box_msg, expected_len=65, key_input=True)
            small = False
        pub_key = ''.join(pub_key.split())

        if key_type == B58_PUB_KEY and pub_key == RESEND:
            return pub_key.encode()

        try:
            return b58decode(pub_key, file_key=(key_type == B58_FILE_KEY))
        except ValueError:
            c_print("Checksum error - Check that entered key is correct.",
                    head=1)
            print_on_previous_line(reps=5 if small else 6, delay=1.5)
Пример #28
0
def show_fingerprints(window: 'TxWindow') -> None:
    """Print domain separated fingerprints of public keys on TxM.

    Comparison of fingerprints over authenticated channel can be
    used to verify users are not under man-in-the-middle attack.
    """
    if window.type == WIN_TYPE_GROUP:
        raise FunctionReturn('Group is selected.')

    if window.contact.tx_fingerprint == bytes(FINGERPRINT_LEN):
        raise FunctionReturn(f"Pre-shared keys have no fingerprints.")

    clear_screen()
    print_fingerprint(window.contact.tx_fingerprint,
                      "   Your fingerprint (you read)   ")
    print_fingerprint(window.contact.rx_fingerprint,
                      "Contact's fingerprint (they read)")
    print('')
Пример #29
0
    def select_group(self, selection: str, cmd: bool,
                     settings: 'Settings') -> None:
        """Select group."""
        if cmd and settings.traffic_masking and selection != self.name:
            raise SoftError(
                "Error: Can't change window during traffic masking.",
                head_clear=True)

        self.contact = None
        self.group = self.group_list.get_group(selection)
        self.window_contacts = self.group.members
        self.name = self.group.name
        self.uid = self.group.group_id
        self.group_id = self.group.group_id
        self.log_messages = self.group.log_messages
        self.type = WIN_TYPE_GROUP
        self.type_print = "group"

        clear_screen()
Пример #30
0
def verify_fingerprints(tx_fp: bytes, rx_fp: bytes) -> bool:
    """\
    Verify fingerprints over out-of-band channel to
    detect MITM attacks against TFC's key exchange.

    :param tx_fp: User's fingerprint
    :param rx_fp: Contact's fingerprint
    :return:      True if fingerprints match, else False
    """
    clear_screen()

    message_printer("To verify received public key was not replaced by attacker in network, "
                    "call the contact over 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=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?")