Exemple #1
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)
Exemple #2
0
    def redraw_file_win(self) -> None:
        """Draw file transmission window progress bars."""
        # Columns
        c1 = ['File name']
        c2 = ['Size']
        c3 = ['Sender']
        c4 = ['Complete']

        for i, p in enumerate(self.packet_list):
            if p.type == FILE and len(p.assembly_pt_list) > 0:
                c1.append(p.name)
                c2.append(p.size)
                c3.append(p.contact.nick)
                c4.append(f"{len(p.assembly_pt_list) / p.packets * 100:.2f}%")

        if not len(c1) > 1:
            c_print("No file transmissions currently in progress.",
                    head=1,
                    tail=1)
            print_on_previous_line(reps=3, delay=0.1)
            return None

        lst = []
        for name, size, sender, percent, in zip(c1, c2, c3, c4):
            lst.append('{0:{1}} {2:{3}} {4:{5}} {6:{7}}'.format(
                name,
                max(len(v) for v in c1) + CONTACT_LIST_INDENT, size,
                max(len(v) for v in c2) + CONTACT_LIST_INDENT, sender,
                max(len(v) for v in c3) + CONTACT_LIST_INDENT, percent,
                max(len(v) for v in c4) + CONTACT_LIST_INDENT))

        lst.insert(1, get_terminal_width() * '─')

        print('\n' + '\n'.join(lst) + '\n')
        print_on_previous_line(reps=len(lst) + 2, delay=0.1)
Exemple #3
0
    def redraw(self):
        """Draw file window frame."""
        ft_found = False
        line_ctr = 0
        longest_title = 0
        tty_w = get_tty_w()

        for p in self.packet_list:
            if p.type == 'file' and len(p.assembly_pt_list) > 0:
                title = "{} ({}) from {} ".format(p.f_name, p.f_size,
                                                  p.contact.nick)
                longest_title = max(longest_title, len(title))

        for p in self.packet_list:
            if p.type == 'file' and len(p.assembly_pt_list) > 0:
                line_ctr += 1
                ft_found = True
                title = "{} ({}) from {} ".format(p.f_name, p.f_size,
                                                  p.contact.nick)
                title += (longest_title - len(title)) * ' '

                bar_len = max(tty_w - (4 + len(title)), 1)
                ready = int((len(p.assembly_pt_list) / p.f_packets) * bar_len)
                missing = bar_len - ready
                bar = title + '[' + (ready -
                                     1) * '=' + '>' + missing * ' ' + ']'
                print(bar)

        print_on_previous_line(reps=line_ctr)

        if not ft_found:
            c_print("No file transmissions currently in progress.",
                    head=1,
                    tail=1)
            print_on_previous_line(reps=3)
Exemple #4
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)
Exemple #5
0
def init_entropy() -> None:
    """Wait until Kernel CSPRNG is sufficiently seeded.

    Wait until entropy_avail file states that system has at least 512 bits of
    entropy. The headroom allows room for error in accuracy of entropy
    collector's entropy estimator; As long as input has at least 4 bits per
    byte of actual entropy, /dev/urandom will be sufficiently seeded when
    it is allowed to generate keys.
    """
    clear_screen()
    phase("Waiting for Kernel CSPRNG random pool to fill up", head=1)

    ent_avail = 0
    threshold = 512

    while ent_avail < threshold:
        try:
            with open('/proc/sys/kernel/random/entropy_avail') as f:
                value = f.read()
            ent_avail = int(value.strip())
            c_print("{}/{}".format(ent_avail, threshold))
            print_on_previous_line(delay=0.01)
        except (KeyboardInterrupt, EOFError):
            pass

    print_on_previous_line()
    phase("Waiting for Kernel CSPRNG random pool to fill up")
    phase("Done")
Exemple #6
0
    def search_serial_interface(self) -> str:
        """Search for a serial interface."""
        if self.settings.session_usb_serial_adapter:
            search_announced = False

            if not self.init_found:
                phase("Searching for USB-to-serial interface", offset=len('Found'))

            while True:
                for f in sorted(os.listdir('/dev/')):
                    if f.startswith('ttyUSB'):
                        if self.init_found:
                            time.sleep(1)
                        phase('Found', done=True)
                        if self.init_found:
                            print_on_previous_line(reps=2)
                        self.init_found = True
                        return f'/dev/{f}'

                time.sleep(0.1)
                if self.init_found and not search_announced:
                    phase("Serial adapter disconnected. Waiting for interface", head=1, offset=len('Found'))
                    search_announced = True

        else:
            if self.settings.built_in_serial_interface in sorted(os.listdir('/dev/')):
                return f'/dev/{self.settings.built_in_serial_interface}'
            raise CriticalError(f"Error: /dev/{self.settings.built_in_serial_interface} was not found.")
Exemple #7
0
def get_b58_key(k_type: str) -> bytes:
    """Ask user to input Base58 encoded public key from RxM."""
    if k_type == 'pubkey':
        clear_screen()
        c_print("Import public key from RxM", head=1, tail=1)
        c_print("WARNING")
        message_printer(
            "Key exchange will break the HW separation. "
            "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 adversary on NH.",
            head=1,
            tail=1)
        box_msg = "Enter contact's public key from RxM"
    elif k_type == 'localkey':
        box_msg = "Enter local key decryption key from TxM"
    elif k_type == 'imported_file':
        box_msg = "Enter file decryption key"
    else:
        raise CriticalError("Invalid key type")

    while True:
        pub_key = box_input(box_msg, expected_len=59)
        pub_key = ''.join(pub_key.split())

        try:
            return b58decode(pub_key)
        except ValueError:
            c_print("Checksum error - Check that entered key is correct.",
                    head=1)
            print_on_previous_line(reps=4, delay=1.5)
Exemple #8
0
    def search_serial_interface(self) -> str:
        """Search for serial interface."""
        if self.settings.session_usb_iface:
            search_announced = False

            if not self.init_found:
                print_on_previous_line()
                phase("Searching for USB-to-serial interface")

            while True:
                time.sleep(0.1)
                for f in sorted(os.listdir('/dev')):
                    if f.startswith('ttyUSB'):
                        if self.init_found:
                            time.sleep(1.5)
                        phase('Found', done=True)
                        if self.init_found:
                            print_on_previous_line(reps=2)
                        self.init_found = True
                        return '/dev/{}'.format(f)
                else:
                    if not search_announced:
                        if self.init_found:
                            phase(
                                "Serial adapter disconnected. Waiting for interface",
                                head=1)
                        search_announced = True

        else:
            f = 'serial0' if 'Raspbian' in platform.platform() else 'ttyS0'
            if f in sorted(os.listdir('/dev/')):
                return '/dev/{}'.format(f)
            else:
                raise CriticalError("Error: /dev/{} was not found.".format(f))
Exemple #9
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)
Exemple #10
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)
Exemple #11
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)
Exemple #12
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)
Exemple #13
0
    def load_master_key(self) -> None:
        """Derive master key from password from stored values (salt, rounds, memory)."""
        ensure_dir(f'{DIR_USER_DATA}/')
        with open(self.file_name, 'rb') as f:
            data = f.read()
        salt = data[0:32]
        k_hash = data[32:64]
        rounds = bytes_to_int(data[64:72])
        memory = bytes_to_int(data[72:80])

        while True:
            password = MasterKey.get_password()
            phase("Deriving master key", head=2, offset=16)
            purp_key, _ = argon2_kdf(password,
                                     salt,
                                     rounds,
                                     memory,
                                     local_testing=self.local_test)
            if hash_chain(purp_key) == k_hash:
                phase("Password correct", done=True)
                self.master_key = purp_key
                clear_screen(delay=0.5)
                break
            else:
                phase("Invalid password", done=True)
                print_on_previous_line(reps=5, delay=1)
Exemple #14
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)
Exemple #15
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
Exemple #16
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)
Exemple #17
0
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)
Exemple #18
0
def win_activity(window_list: 'WindowList') -> None:
    """Show number of unread messages in each window."""
    unread_wins = [w for w in window_list if (w.uid != WIN_UID_COMMAND and w.unread_messages > 0)]
    print_list  = ["Window activity"] if unread_wins else ["No window activity"]
    print_list += [f"{w.name}: {w.unread_messages}" for w in unread_wins]

    m_print(print_list, box=True)
    print_on_previous_line(reps=(len(print_list) + 2), delay=1)
Exemple #19
0
def box_input(title: str,
              default: str = '',
              head: int = 0,
              tail: int = 0,
              expected_len: int = 0,
              validator: Callable = None,
              validator_args: Any = None) -> str:
    """Display boxed prompt for user with title.

    :param title:          Title for data to prompt
    :param default:        Default return value
    :param head:           Number of new lines to print before input
    :param tail:           Number of new lines to print after input
    :param expected_len    Expected length of input
    :param validator:      Input validator function
    :param validator_args: Arguments required by the validator
    :return:               Input from user
    """
    for _ in range(head):
        print('')

    tty_w = get_tty_w()
    input_len = tty_w - 2 if expected_len == 0 else expected_len + 2

    input_top_line = '┌' + input_len * '─' + '┐'
    input_line = '│' + input_len * ' ' + '│'
    input_bot_line = '└' + input_len * '─' + '┘'

    input_line_indent = (tty_w - len(input_line)) // 2
    input_box_indent = input_line_indent * ' '

    print(input_box_indent + input_top_line)
    print(input_box_indent + input_line)
    print(input_box_indent + input_bot_line)
    print(4 * CURSOR_UP_ONE_LINE)
    print(input_box_indent + '┌─┤' + title + '├')

    user_input = input(input_box_indent + '│ ')

    if user_input == '':
        print(2 * CURSOR_UP_ONE_LINE)
        print(input_box_indent + f'│ {default}')
        user_input = default

    if validator is not None:
        success, error_msg = validator(user_input, validator_args)
        if not success:
            c_print("Error: {}".format(error_msg), head=1)
            print_on_previous_line(reps=4, delay=1.5)
            return box_input(title, default, head, tail, expected_len,
                             validator, validator_args)

    for _ in range(tail):
        print('')

    return user_input
Exemple #20
0
def show_win_activity(window_list: 'WindowList') -> None:
    """Show number of unread messages in each window."""
    unread_wins = [
        w for w in window_list if (w.uid != 'local' and w.unread_messages > 0)
    ]
    print_list = ["Window activity"] if unread_wins else ["No window activity"]
    for w in unread_wins:
        print_list.append(f"{w.name}: {w.unread_messages}")
    box_print(print_list)
    print_on_previous_line(reps=(len(print_list) + 2), delay=1.5)
Exemple #21
0
 def check_empty_group(self) -> None:
     """Notify the user if group was empty."""
     if self.type == 'message' and self.window.type == 'group' and len(
             self.window.window_contacts) == 0:
         print_on_previous_line()
         print(
             f"Msg to {self.w_type}{self.window.name}: Error: Group is empty."
         )
         print_on_previous_line(delay=0.5)
         self.__init__(self.window, self.settings)
Exemple #22
0
    def print(self, msg_tuple: MsgTuple, file: Any = None) -> None:
        """Print a new message to the window."""

        # Unpack tuple
        ts, message, onion_pub_key, origin, whisper, event_msg = msg_tuple

        # Determine handle
        handle = self.get_handle(ts, onion_pub_key, origin, whisper, event_msg)

        # Check if message content needs to be changed to privacy-preserving notification
        if not self.is_active and not self.settings.new_message_notify_preview and self.uid != WIN_UID_COMMAND:
            trailer = 's' if self.unread_messages > 0 else ''
            message = BOLD_ON + f"{self.unread_messages + 1} unread message{trailer}" + NORMAL_TEXT

        # Wrap message
        wrapper = textwrap.TextWrapper(width=get_terminal_width(),
                                       initial_indent=handle,
                                       subsequent_indent=len(handle) * ' ')
        wrapped = wrapper.fill(message)
        if wrapped == '':
            wrapped = handle

        # Add bolding unless export file is provided
        bold_on, bold_off, f_name = (BOLD_ON, NORMAL_TEXT,
                                     sys.stdout) if file is None else ('', '',
                                                                       file)
        wrapped = bold_on + wrapped[:len(handle
                                         )] + bold_off + wrapped[len(handle):]

        if self.is_active:
            if self.previous_msg_ts.date() != ts.date():
                print(bold_on + f"00:00 -!- Day changed to {str(ts.date())}" +
                      bold_off,
                      file=f_name)
            print(wrapped, file=f_name)

        else:
            if onion_pub_key != WIN_UID_COMMAND:
                self.unread_messages += 1

            if (self.type == WIN_TYPE_CONTACT and self.contact is not None and self.contact.notifications) \
            or (self.type == WIN_TYPE_GROUP   and self.group   is not None and self.group.notifications) \
            or (self.type == WIN_TYPE_COMMAND):

                lines = wrapped.split('\n')
                if len(lines) > 1:
                    print(lines[0][:-1] +
                          '…')  # Preview only first line of the long message
                else:
                    print(wrapped)
                print_on_previous_line(
                    delay=self.settings.new_message_notify_duration,
                    flush=True)

        self.previous_msg_ts = ts
Exemple #23
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}: ")
        password_2 = pwd_prompt(f"Confirm the {purpose}: ", repeat=True)

        if password_1 == password_2:
            return password_1
        else:
            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)
Exemple #24
0
def rxm_show_cmd_win(window:   'Window',
                     settings: 'Settings',
                     c_queue:  'Queue') -> None:
    """Show command window on RxM until user presses Enter."""
    packet = WINDOW_CHANGE_HEADER + LOCAL_WIN_ID_BYTES
    queue_command(packet, settings, c_queue)

    box_print(f"<Enter> returns RxM to {window.name}'s window", manual_proceed=True)
    print_on_previous_line(reps=4, flush=True)

    packet = WINDOW_CHANGE_HEADER + window.uid.encode()
    queue_command(packet, settings, c_queue)
Exemple #25
0
def rxm_display_f_win(window:   'Window',
                      settings: 'Settings',
                      c_queue:  'Queue'):
    """Show file reception window on RxM until user presses Enter."""
    packet = WINDOW_CHANGE_HEADER + FILE_R_WIN_ID_BYTES
    queue_command(packet, settings, c_queue)

    box_print(f"<Enter> returns RxM to {window.name}'s window", manual_proceed=True)
    print_on_previous_line(reps=4, flush=True)

    packet = WINDOW_CHANGE_HEADER + window.uid.encode()
    queue_command(packet, settings, c_queue)
Exemple #26
0
def yes(
    prompt: str,  # Question to be asked
    abort: Optional[bool] = None,  # Determines the return value of ^C and ^D
    head: int = 0,  # Number of new lines to print before prompt
    tail: int = 0  # Number of new lines to print after prompt
) -> bool:  # True/False depending on input
    """Prompt the user a question that is answered with yes/no."""
    print_spacing(head)

    prompt = f"{prompt} (y/n): "
    input_space = len(' yes ')

    upper_line = '┌' + (len(prompt) + input_space) * '─' + '┐'
    title_line = '│' + prompt + input_space * ' ' + '│'
    lower_line = '└' + (len(prompt) + input_space) * '─' + '┘'

    terminal_w = get_terminal_width()
    upper_line = upper_line.center(terminal_w)
    title_line = title_line.center(terminal_w)
    lower_line = lower_line.center(terminal_w)

    indent = title_line.find('│')

    terminal_width_check(len(upper_line))

    print(upper_line)
    while True:
        print(title_line)
        print(lower_line)
        print(3 * CURSOR_UP_ONE_LINE)

        try:
            user_input = input(indent * ' ' + f'│ {prompt}')
        except (EOFError, KeyboardInterrupt):
            if abort is None:
                raise
            print('')
            user_input = 'y' if abort else 'n'

        print_on_previous_line()

        if user_input == '':
            continue

        if user_input.lower() in ['y', 'yes']:
            print(indent * ' ' + f'│ {prompt}Yes │\n')
            print_spacing(tail)
            return True

        if user_input.lower() in ['n', 'no']:
            print(indent * ' ' + f'│ {prompt}No  │\n')
            print_spacing(tail)
            return False
Exemple #27
0
def yes(prompt: str, head: int = 0, tail: int = 0) -> bool:
    """Prompt user a question that is answered with yes / no.

    :param prompt: Question to be asked
    :param head:   Number of new lines to print before prompt
    :param tail:   Number of new lines to print after prompt
    :return:       True if user types 'y' or 'yes'
                   False if user types 'n' or 'no'
    """
    for _ in range(head):
        print('')

    prompt = "{} (y/n): ".format(prompt)
    tty_w = get_tty_w()
    upper_line = ('┌' + (len(prompt) + 5) * '─' + '┐')
    title_line = ('│' + prompt + 5 * ' ' + '│')
    lower_line = ('└' + (len(prompt) + 5) * '─' + '┘')

    upper_line = upper_line.center(tty_w)
    title_line = title_line.center(tty_w)
    lower_line = lower_line.center(tty_w)

    indent = title_line.find('│')
    print(upper_line)
    print(title_line)
    print(lower_line)
    print(3 * CURSOR_UP_ONE_LINE)

    while True:
        print(title_line)
        print(lower_line)
        print(3 * CURSOR_UP_ONE_LINE)
        answer = input(indent * ' ' + f'│ {prompt}')
        print_on_previous_line()

        if answer == '':
            continue

        if answer.lower() in 'yes':
            print(indent * ' ' + f'│ {prompt}Yes │\n')
            for _ in range(tail):
                print('')
            return True

        elif answer.lower() in 'no':
            print(indent * ' ' + f'│ {prompt}No  │\n')
            for _ in range(tail):
                print('')
            return False

        else:
            continue
Exemple #28
0
    def process_aliases(self) -> None:
        """Check if input was an alias for existing command."""
        aliases = [(' ', '/unread'),
                   ('  ',
                    '/exit' if self.settings.double_space_exits else '/clear'),
                   ('//', '/cmd')]

        for a in aliases:
            if self.plaintext == a[0]:
                self.plaintext = a[1]
                print_on_previous_line()
                print(
                    f"Msg to {self.w_type}{self.window.name}: {self.plaintext}"
                )
Exemple #29
0
    def new_password(cls, purpose: str = "master password") -> str:
        """Prompt user to enter and confirm a new password."""
        password_1 = pwd_prompt(f"Enter a new {purpose}: ", '┌', '┐')
        password_2 = pwd_prompt(f"Confirm the {purpose}: ", '├', '┤')

        if password_1 == password_2:
            return password_1
        else:
            c_print("Error: Passwords did not match. Try again.",
                    head=1,
                    tail=1)
            time.sleep(1)
            print_on_previous_line(reps=7)
            return cls.new_password(purpose)
Exemple #30
0
    def determine_memory_cost(
        self,
        password: str,
        salt: bytes,
        time_cost: int,
        memory_cost: int,
        parallelism: int,
    ) -> Tuple[int, bytes]:
        """Determine suitable memory_cost value for Argon2id.

        If we reached this function, it means we found a `t+1` value for
        time_cost (explained in the `determine_time_cost` function). We
        therefore do a binary search on the amount of memory to use
        until we hit the desired key derivation time range.
        """
        lower_bound = ARGON2_MIN_MEMORY_COST
        upper_bound = memory_cost

        while True:
            memory_cost = int(round((lower_bound + upper_bound) // 2, -3))

            print_on_previous_line()
            phase(f"Trying memory cost {memory_cost} KiB")
            master_key, kd_time = self.timed_key_derivation(
                password, salt, time_cost, memory_cost, parallelism)
            phase(f"{kd_time:.1f}s", done=True)

            # If we found a suitable memory_cost value, we accept the key and the memory_cost.
            if MIN_KEY_DERIVATION_TIME <= kd_time <= MAX_KEY_DERIVATION_TIME:
                return memory_cost, master_key

            # The search might fail e.g. if external CPU load causes delay in key
            # derivation, which causes the search to continue into wrong branch. In
            # such a situation the search is restarted. The binary search is problematic
            # with tight key derivation time target ranges, so if the search keeps
            # restarting, increasing MAX_KEY_DERIVATION_TIME (and thus expanding the
            # range) will help finding suitable memory_cost value faster. Increasing
            # MAX_KEY_DERIVATION_TIME slightly affects security (positively) and user
            # experience (negatively).
            if memory_cost == lower_bound or memory_cost == upper_bound:
                lower_bound = ARGON2_MIN_MEMORY_COST
                upper_bound = self.get_available_memory()
                continue

            if kd_time < MIN_KEY_DERIVATION_TIME:
                lower_bound = memory_cost

            elif kd_time > MAX_KEY_DERIVATION_TIME:
                upper_bound = memory_cost