示例#1
0
文件: crypto.py 项目: barleyj/tfc
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")
示例#2
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)
示例#3
0
def process_local_key(packet: bytes, contact_list: 'ContactList',
                      key_list: 'KeyList') -> None:
    """Decrypt local key packet, add local contact/keyset."""
    try:
        clear_screen()
        box_print(["Received encrypted local key"], tail=1)

        kdk = get_b58_key('localkey')

        try:
            pt = auth_and_decrypt(packet[1:], key=kdk, soft_e=True)
        except nacl.exceptions.CryptoError:
            raise FunctionReturn("Invalid key decryption key.", 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', 'local', 'local', bytes(32),
                                 bytes(32), False, False, True)

        # Add local contact to keyset database
        key_list.add_keyset('local', key, bytes(32), hek, bytes(32))
        box_print([f"Confirmation code for TxM: {conf_code.hex()}"], head=1)

    except KeyboardInterrupt:
        raise FunctionReturn("Local key setup aborted.", delay=1)
示例#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:
    """Derive new master key based on master password delivered by TxM."""
    old_master_key = master_key.master_key[:]
    master_key.new_master_key()
    new_master_key = master_key.master_key

    ensure_dir(f'{DIR_USER_DATA}/')
    file_name = f'{DIR_USER_DATA}/{settings.software_operation}_logs'
    if os.path.isfile(file_name):
        phase("Re-encrypting log-file")
        re_encrypt(old_master_key, new_master_key, settings)
        phase('Done')

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

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

    local_win = window_list.get_window('local')
    local_win.print_new(ts, "Changed RxM master key.", print_=False)
示例#5
0
def psk_command(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList',
                contact_list: 'ContactList', key_list: 'KeyList',
                settings: 'Settings', pubkey_buf: Dict[str, str]) -> 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, 'user_placeholder', nick, bytes(32),
                             bytes(32), settings.log_msg_by_default,
                             settings.store_file_default,
                             settings.n_m_notify_privacy)

    # The Rx-side keys are set as null-byte strings to indicate they have not been added yet.
    key_list.add_keyset(account, tx_key, bytes(32), tx_hek, bytes(32))

    pubkey_buf.pop(account, None)

    local_win = window_list.get_window('local')
    local_win.print_new(ts,
                        f"Added Tx-PSK for {nick} ({account}).",
                        print_=False)

    box_print([f"Successfully added {nick}."])
    clear_screen(delay=1)
示例#6
0
def ecdhe_command(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList',
                  contact_list: 'ContactList', key_list: 'KeyList',
                  settings: 'Settings', pubkey_buf: Dict[str, str]) -> None:
    """Add contact and it's X25519 keys."""
    tx_key = cmd_data[0:32]
    tx_hek = cmd_data[32:64]
    rx_key = cmd_data[64:96]
    rx_hek = cmd_data[96:128]

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

    contact_list.add_contact(account, 'user_placeholder', nick, bytes(32),
                             bytes(32), settings.log_msg_by_default,
                             settings.store_file_default,
                             settings.n_m_notify_privacy)

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

    pubkey_buf.pop(account, None)

    local_win = window_list.get_window('local')
    local_win.print_new(ts,
                        f"Added X25519 keys for {nick} ({account}).",
                        print_=False)

    box_print([f"Successfully added {nick}."])
    clear_screen(delay=1)
示例#7
0
def process_public_key(ts: 'datetime', packet: bytes,
                       window_list: 'WindowList', settings: 'Settings',
                       pubkey_buf: Dict[str, str]) -> None:
    """Display public from contact."""
    pub_key = packet[1:33]
    origin = packet[33:34]
    account = packet[34:].decode()

    if origin == ORIGIN_CONTACT_HEADER:
        pub_key_enc = b58encode(pub_key)
        ssl = {48: 8, 49: 7, 50: 5}.get(len(pub_key_enc), 5)
        pub_key_enc = pub_key_enc if settings.local_testing_mode else ' '.join(
            split_string(pub_key_enc, item_len=ssl))

        pubkey_buf[account] = pub_key_enc

        box_print(
            [f"Received public key from {account}", '', pubkey_buf[account]],
            head=1,
            tail=1)

        local_win = window_list.get_local_window()
        local_win.print_new(
            ts,
            f"Received public key from {account}: {pub_key_enc}",
            print_=False)

    if origin == ORIGIN_USER_HEADER and account in pubkey_buf:
        clear_screen()
        box_print([f"Public key for {account}", '', pubkey_buf[account]],
                  head=1,
                  tail=1)
示例#8
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)
示例#9
0
def new_psk(account: str, user: str, nick: str, contact_list: 'ContactList',
            settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None:
    """Generate new pre-shared key for manual key delivery.

    :param account:      The contact's account name (e.g. [email protected])
    :param user:         The user's account name (e.g. [email protected])
    :param nick:         Nick of contact
    :param contact_list: Contact list object
    :param settings:     Settings object
    :param queues:       Dictionary of multiprocessing queues
    :return:             None
    """
    try:
        tx_key = keygen()
        tx_hek = keygen()
        salt = keygen()
        password = MasterKey.new_password("password for PSK")

        phase("Deriving key encryption key", head=2)
        kek, _ = argon2_kdf(password,
                            salt,
                            rounds=16,
                            memory=128000,
                            parallelism=1)
        phase('Done')

        ct_tag = encrypt_and_sign(tx_key + tx_hek, key=kek)
        store_d = ask_path_gui(f"Select removable media for {nick}", settings)
        f_name = f"{store_d}/{user}.psk - Give to {account}"

        try:
            with open(f_name, 'wb+') as f:
                f.write(salt + ct_tag)
        except PermissionError:
            raise FunctionReturn(
                "Error: Did not have permission to write to directory.")

        packet = KEY_EX_PSK_TX_HEADER \
                 + tx_key \
                 + tx_hek \
                 + account.encode() + US_BYTE +  nick.encode()

        queue_command(packet, settings, queues[COMMAND_PACKET_QUEUE])

        contact_list.add_contact(account, user, nick, bytes(32), bytes(32),
                                 settings.log_msg_by_default,
                                 settings.store_file_default,
                                 settings.n_m_notify_privacy)

        queues[KEY_MANAGEMENT_QUEUE].put(
            ('ADD', account, tx_key, bytes(32), tx_hek, bytes(32)))

        box_print([f"Successfully added {nick}."], head=1)
        clear_screen(delay=1)

    except KeyboardInterrupt:
        raise FunctionReturn("PSK generation aborted.")
示例#10
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_serial_adapter = yes(
                 "Does TxM use USB-to-serial/TTL adapter?", head=1, tail=1)
         else:
             self.rxm_serial_adapter = yes(
                 "Does RxM use USB-to-serial/TTL adapter?", head=1, tail=1)
示例#11
0
文件: commands.py 项目: barleyj/tfc
def print_about() -> None:
    """Print URLs that direct to TFC project site and documentation."""
    from tfc import __version__

    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")
示例#12
0
def local_key_installed(ts: 'datetime', window_list: 'WindowList',
                        contact_list: 'ContactList') -> None:
    """Clear local key bootstrap process from screen."""
    local_win = window_list.get_window('local')
    local_win.print_new(ts, "Created a new local key.", print_=False)

    box_print(["Successfully added a new local key."])
    clear_screen(delay=1)

    if not contact_list.has_contacts():
        clear_screen()
        c_print("Waiting for new contacts", head=1, tail=1)
示例#13
0
文件: commands.py 项目: barleyj/tfc
def clear_screens(window:   'Window',
                  settings: 'Settings',
                  c_queue:  'Queue',
                  gateway:  'Gateway') -> None:
    """Clear TxM, RxM and NH screens."""
    clear_screen()
    queue_command(CLEAR_SCREEN_HEADER, settings, c_queue)
    if not settings.session_trickle:
        if window.imc_name is not None:
            im_window = window.imc_name.encode()
            time.sleep(0.5)
            transmit(UNENCRYPTED_PACKET_HEADER + UNENCRYPTED_SCREEN_CLEAR + im_window, settings, gateway)
示例#14
0
文件: windows.py 项目: barleyj/tfc
    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_trickle and selection != self.uid:
                raise FunctionReturn(
                    "Can't change window during trickle connection.")

            self.group = self.group_list.get_group(selection)
            self.window_contacts = self.group.members
            self.name = self.group.name
            self.uid = self.name
            self.type = '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_trickle:
                contact = self.contact_list.get_contact(selection)
                if self.uid != contact.rx_account:
                    raise FunctionReturn(
                        "Can't change window during trickle connection.")

            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.type = 'contact'

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

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

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

        clear_screen()
示例#15
0
    def __init__(self,
                 return_msg: str,
                 output: bool = True,
                 delay: float = 0,
                 window: Any = None) -> None:
        self.message = return_msg

        if window is None:
            if output:
                clear_screen()
                c_print(self.message, head=1, tail=1)
            time.sleep(delay)
        else:
            window.print_new(datetime.datetime.now(), return_msg)
示例#16
0
文件: contact.py 项目: barleyj/tfc
def fingerprints(window: 'Window') -> None:
    """Print domain separated fingerprints of shared secret on TxM."""
    if window.type == 'group':
        raise FunctionReturn('Group is selected.')

    if window.contact.tx_fingerprint == bytes(32):
        raise FunctionReturn(
            f"Key have been pre-shared with {window.name} and thus have no fingerprints."
        )

    clear_screen()
    print_fingerprints(window.contact.tx_fingerprint,
                       "   Your fingerprint (you read)   ")
    print_fingerprints(window.contact.rx_fingerprint,
                       "Contact's fingerprint (they read)")
    print('')
示例#17
0
文件: commands.py 项目: barleyj/tfc
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_trickle:
            raise FunctionReturn("Command disabled during trickle connection.")

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

        if device.lower() not in ['tx', 'txm', 'rx', 'rxm']:
            raise FunctionReturn("Invalid target system.")

        if device.lower() in ['rx', 'rxm']:
            queue_command(CHANGE_MASTER_K_HEADER, settings, queues[COMMAND_PACKET_QUEUE])
            print('')
            return None

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

        ensure_dir(f'{DIR_USER_DATA}/')
        file_name = f'{DIR_USER_DATA}/{settings.software_operation}_logs'
        if os.path.isfile(file_name):
            phase("Re-encrypting log-file")
            re_encrypt(old_master_key, new_master_key, settings)
            phase("Done")

        queues[KEY_MANAGEMENT_QUEUE].put(('KEY', master_key))

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

        box_print("Master key successfully changed.", head=1)
        clear_screen(delay=1.5)
    except KeyboardInterrupt:
        raise FunctionReturn("Password change aborted.")
示例#18
0
文件: contact.py 项目: barleyj/tfc
def add_new_contact(contact_list: 'ContactList', group_list: 'GroupList',
                    settings: 'Settings', queues: Dict[bytes, 'Queue'],
                    gateway: 'Gateway') -> None:
    """Prompt for contact account details and initialize desired key exchange method."""
    try:
        if settings.session_trickle:
            raise FunctionReturn("Command disabled during trickle connection.")

        if len(contact_list) >= settings.m_number_of_accnts:
            raise FunctionReturn(
                f"Error: TFC settings only allow {settings.m_number_of_accnts} accounts."
            )

        clear_screen()
        c_print("Add new contact", head=1)

        acco = box_input("Contact account", tail=1,
                         validator=validate_account).strip()
        user = box_input("Your account", tail=1,
                         validator=validate_account).strip()
        defn = acco.split('@')[0].capitalize()
        nick = box_input(f"Contact nick [{defn}]",
                         default=defn,
                         tail=1,
                         validator=validate_nick,
                         validator_args=(contact_list, group_list,
                                         acco)).strip()
        keyx = box_input("Key exchange ([ECDHE],PSK) ",
                         default='ECDHE',
                         tail=1,
                         validator=validate_key_exchange).strip()

        if keyx.lower() in 'ecdhe':
            start_key_exchange(acco, user, nick, contact_list, settings,
                               queues, gateway)

        elif keyx.lower() in 'psk':
            new_psk(acco, user, nick, contact_list, settings, queues)

    except KeyboardInterrupt:
        raise FunctionReturn("Contact creation aborted.")
示例#19
0
def verify_fingerprints(tx_fp: bytes, rx_fp: bytes) -> bool:
    """Verify fingerprints over off-band channel to detect MITM attacks between NHs.

    :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 the public key was not swapped during delivery, "
        "call your contact over end-to-end encrypted line, preferably "
        "Signal by Open Whisper Systems. Verify call's Short "
        "Authentication String and then compare fingerprints below.",
        head=1,
        tail=1)

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

    return yes("Is the contact's fingerprint correct?")
示例#20
0
    def print_contacts(self, spacing: bool = True) -> None:
        """Print list of contacts."""
        # Columns
        c1 = ['Contact']
        c2 = ['Logging']
        c3 = ['Notify']
        c4 = ['Files ']
        c5 = ['Key Ex']
        c6 = ['Account']

        for c in self.contacts:
            if c.rx_account == 'local':
                continue

            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(32) else 'X25519')
            c6.append(c.rx_account)

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

        if spacing:
            clear_screen()
            print('')

        lst.insert(1, get_tty_w() * '─')
        print('\n'.join(str(l) for l in lst))
        print('\n')
示例#21
0
def start_key_exchange(account: str, user: str, nick: str,
                       contact_list: 'ContactList', settings: 'Settings',
                       queues: Dict[bytes,
                                    'Queue'], gateway: 'Gateway') -> None:
    """Start X25519 key exchange with recipient.

    Variable naming:

        tx     = user's key                 rx  = contact's key
        sk     = private (secret) key       pk  = public key
        key    = message key                hek = header key
        dh_ssk = DH shared secret

    :param account:      The contact's account name (e.g. [email protected])
    :param user:         The user's account name (e.g. [email protected])
    :param nick:         Contact's nickname
    :param contact_list: Contact list object
    :param settings:     Settings object
    :param queues:       Dictionary of multiprocessing queues
    :param gateway:      Gateway object
    :return:             None
    """
    try:
        tx_sk = nacl.public.PrivateKey.generate()
        tx_pk = bytes(tx_sk.public_key)

        transmit(
            PUBLIC_KEY_PACKET_HEADER + tx_pk + user.encode() + US_BYTE +
            account.encode(), settings, gateway)

        rx_pk = nacl.public.PublicKey(get_b58_key('pubkey'))
        dh_box = nacl.public.Box(tx_sk, rx_pk)
        dh_ssk = dh_box.shared_key()
        rx_pk = bytes(rx_pk)

        # Domain separate each key with key-type specific byte-string and
        # with public keys that both clients know which way to place.
        tx_key = hash_chain(dh_ssk + rx_pk + b'message_key')
        rx_key = hash_chain(dh_ssk + tx_pk + b'message_key')

        tx_hek = hash_chain(dh_ssk + rx_pk + b'header_key')
        rx_hek = hash_chain(dh_ssk + tx_pk + b'header_key')

        # Domain separate fingerprints of public keys by using the shared
        # secret as salt. This way entities who might monitor fingerprint
        # verification channel are unable to correlate spoken values with
        # public keys that transit through a compromised IM server. This
        # protects against deanonymization of IM accounts in cases where
        # clients connect to the compromised server via Tor.
        tx_fp = hash_chain(dh_ssk + tx_pk + b'fingerprint')
        rx_fp = hash_chain(dh_ssk + rx_pk + b'fingerprint')

        if not verify_fingerprints(tx_fp, rx_fp):
            box_print([
                "Possible man-in-the-middle attack detected.",
                "Aborting key exchange for your safety."
            ],
                      tail=1)
            raise FunctionReturn("Fingerprint mismatch",
                                 output=False,
                                 delay=2.5)

        packet = KEY_EX_ECDHE_HEADER \
                 + tx_key + tx_hek \
                 + rx_key + rx_hek \
                 + account.encode() + US_BYTE + nick.encode()

        queue_command(packet, settings, queues[COMMAND_PACKET_QUEUE])

        contact_list.add_contact(account, user, nick, tx_fp, rx_fp,
                                 settings.log_msg_by_default,
                                 settings.store_file_default,
                                 settings.n_m_notify_privacy)

        # Null-bytes below are fillers for Rx-keys not used by TxM.
        queues[KEY_MANAGEMENT_QUEUE].put(
            ('ADD', account, tx_key, bytes(32), tx_hek, bytes(32)))

        box_print([f"Successfully added {nick}."])
        clear_screen(delay=1)

    except KeyboardInterrupt:
        raise FunctionReturn("Key exchange aborted.", delay=1)
示例#22
0
文件: commands.py 项目: barleyj/tfc
def print_help(settings: 'Settings') -> None:
    """Print the list of commands."""

    def help_printer(tuple_list: List[Union[Tuple[str, str, bool]]]) -> None:
        """Print help menu, style depending on terminal width and display conditions.

        :param tuple_list: List of command-description-display tuples
        """
        longest_command = ''
        for t in tuple_list:
            longest_command = max(t[0], longest_command, key=len)
        longest_command += ' '  # Add spacing

        for help_cmd, description, display_condition in tuple_list:

            if not display_condition:
                continue

            wrapper    = textwrap.TextWrapper(width=max(1, (get_tty_w() - len(longest_command))))
            desc_lines = wrapper.fill(description).split('\n')
            spacing    = (len(longest_command) - len(help_cmd)) * ' '

            print(help_cmd + spacing + desc_lines[0])

            # Print wrapped description lines with indent
            if len(desc_lines) > 1:
                for line in desc_lines[1:]:
                    print(len(longest_command) * ' ' + line)
                print('')

    common = [("/about",                    "Show links to project resources",                   True),
              ("/add",                      "Add new contact",                                   not settings.session_trickle),
              ("/cf",                       "Cancel file transmission to recipients",            True),
              ("/cm",                       "Cancel message transmission to recipients",         True),
              ("/clear, '  '",              "Clear screens from TxM, RxM and IM client",         True),
              ("/cmd, '//'",                "Display command window on RxM",                     True),
              ("/exit",                     "Exit TFC on TxM, NH and RxM",                       True),
              ("/export (n)",               "Export (n) messages from recipient's logfile",      True),
              ("/file",                     "Send file to active contact/group",                 True),
              ("/fingerprints",             "Print public key fingerprints of user and contact", True),
              ("/fe",                       "Encrypt and export file to NH",                     not settings.session_trickle),
              ("/fi",                       "Import file from NH to RxM",                        not settings.session_trickle),
              ("/fw",                       "Display file reception window on RxM",              True),
              ("/help",                     "Display this list of commands",                     True),
              ("/history (n)",              "Print (n) messages from recipient's logfile",       True),
              ("/localkey",                 "Generate new local key pair",                       not settings.session_trickle),
              ("/logging {on,off}(' all')", "Change log_messages setting (for all contacts)",    True),
              ("/msg",                      "Change active recipient",                           not settings.session_trickle),
              ("/names",                    "List contacts and groups",                          True),
              ("/nick N",                   "Change nickname of active recipient to N",          True),
              ("/notify {on,off} (' all')", "Change notification settings (for all contacts)",   True),
              ("/passwd {tx,rx}",           "Change master password on TxM/RxM",                 not settings.session_trickle),
              ("/psk",                      "Open PSK import dialog on RxM",                     True),
              ("/reset",                    "Reset ephemeral session log on TxM/RxM/IM client",  not settings.session_trickle),
              ("/rm A",                     "Remove account A from TxM and RxM",                 not settings.session_trickle),
              ("/set S V",                  "Change setting S to value V on TxM/RxM",            not settings.session_trickle),
              ("/settings",                 "List settings, default values and descriptions",    not settings.session_trickle),
              ("/store {on,off} (' all')",  "Change file reception (for all contacts)",          True),
              ("/unread, ' '",              "List windows with unread messages on RxM",          True),
              ("Shift + PgUp/PgDn",         "Scroll terminal up/down",                           True)]

    groupc = [("/group create G A1 .. An",  "Create group G and add accounts A1 .. An",          not settings.session_trickle),
              ("/group add G A1 .. An",     "Add accounts A1 .. An to group G",                  not settings.session_trickle),
              ("/group rm G A1 .. An",      "Remove accounts A1 .. An from group G",             not settings.session_trickle),
              ("/group rm G",               "Remove group G",                                    not settings.session_trickle)]

    terminal_width = get_tty_w()

    clear_screen()

    print(textwrap.fill("List of commands:", width=terminal_width))
    print('')
    help_printer(common)
    print(terminal_width * '-')

    if settings.session_trickle:
        print('')
    else:
        print("Group management:\n")
        help_printer(groupc)
        print(terminal_width * '-' + '\n')
示例#23
0
def clear_active_window() -> None:
    """Clear active screen."""
    clear_screen()
示例#24
0
def select_win_cmd(cmd_data: bytes, window_list: 'WindowList') -> None:
    """Select window specified by TxM."""
    window_uid = cmd_data.decode()
    if cmd_data == FILE_R_WIN_ID_BYTES:
        clear_screen()
    window_list.select_rx_window(window_uid)
示例#25
0
 def clear_window() -> None:
     """Clear window."""
     clear_screen()
示例#26
0
    def print_settings(self) -> None:
        """Print list of settings, their current and default values and setting descriptions."""

        # Common
        desc_d = {
            "format_of_logfiles": "Timestamp format of logged messages",
            "disable_gui_dialog":
            "True replaces Tkinter dialogs with CLI prompts",
            "m_members_in_group":
            "Max members in group (Must be same on TxM/RxM)",
            "m_number_of_groups":
            "Max number of groups (Must be same on TxM/RxM)",
            "m_number_of_accnts":
            "Max number of accounts (Must be same on TxM/RxM)",
            "serial_iface_speed":
            "The speed of serial interface in bauds per sec",
            "e_correction_ratio":
            "N/o byte errors serial datagrams can recover from",
            "log_msg_by_default": "Default logging setting for new contacts",
            "store_file_default":
            "True accepts files from new contacts by default",
            "n_m_notify_privacy":
            "Default message notification setting for new contacts",
            "log_dummy_file_a_p":
            "False disables storage of placeholder data for files",

            # TxM
            "txm_serial_adapter":
            "False uses system's integrated serial interface",
            "nh_bypass_messages": "False removes NH bypass interrupt messages",
            "confirm_sent_files":
            "False sends files without asking for confirmation",
            "double_space_exits":
            "True exits with doubles space, else clears screen",
            "trickle_connection":
            "True enables trickle connection to hide metadata",
            "trickle_stat_delay": "Static delay between trickle packets",
            "trickle_rand_delay": "Max random delay for timing obfuscation",
            "long_packet_rand_d": "True adds spam guard evading delay",
            "max_val_for_rand_d":
            "Maximum time for random spam guard evasion delay",

            # RxM
            "rxm_serial_adapter":
            "False uses system's integrated serial interface",
            "new_msg_notify_dur":
            "Number of seconds new msg notification appears"
        }

        clear_screen()
        tty_w = get_tty_w()

        print(
            "Setting name        Current value      Default value      Description"
        )
        print(tty_w * '-')

        for key in self.defaults:
            def_value = str(self.defaults[key]).ljust(len('%Y-%m-%d %H:%M:%S'))
            description = desc_d[key]
            wrapper = textwrap.TextWrapper(width=max(1, (tty_w - 59)))
            desc_lines = wrapper.fill(description).split('\n')
            current_value = str(self.__getattribute__(key)).ljust(17)

            print(f"{key}  {current_value}  {def_value}  {desc_lines[0]}")

            # Print wrapped description lines with indent
            if len(desc_lines) > 1:
                for line in desc_lines[1:]:
                    print(58 * ' ' + line)
                print('')

        print('\n')
示例#27
0
 def test_clear_screen(self):
     self.assertIsNone(clear_screen())
示例#28
0
文件: db_logs.py 项目: barleyj/tfc
def access_history(window:       Union['Window', 'Window_'],
                   contact_list: 'ContactList',
                   settings:     'Settings',
                   master_key:   'MasterKey',
                   msg_to_load:  int = 0,
                   export:       bool = False) -> None:
    """Decrypt 'msg_to_load' last messages from log database and display/export it.

    :param window:       Window object
    :param contact_list: ContactList object
    :param settings:     Settings object
    :param master_key:   Master key object
    :param msg_to_load:  Number of messages to load
    :param export:       When True, write logged messages into
                         plaintext file instead of printing them.
    :return:             None
    """

    def read_entry():
        """Read encrypted log entry.

        Length  |  Data type
        --------|--------------------------------
             24 |  XSalsa20 nonce
              4 |  Timestamp
              4 |  UTF-32 BOM
          4*255 |  Padded account (UTF-32)
              1 |  Origin header
              1 |  Assembly packet header
            255 |  Padded assembly packet (UTF-8)
             16 |  Poly1305 tag
        """
        return log_file.read(1325)

    ensure_dir(f'{DIR_USER_DATA}/')
    file_name = f'{DIR_USER_DATA}/{settings.software_operation}_logs'
    if not os.path.isfile(file_name):
        raise FunctionReturn(f"Error: Could not find '{file_name}'.")

    log_file          = open(file_name, 'rb')
    ts_message_list   = []  # type: List[Tuple[str, str, bytes, str]]
    assembly_p_buffer = dict()
    group_timestamp   = b''

    for ct in iter(read_entry, b''):
        pt      = auth_and_decrypt(ct, key=master_key.master_key)
        account = bytes_to_str(pt[5:1029])

        if window.type == 'contact' and window.uid != account:
            continue

        t_stamp         = parse_ts_bytes(pt[0:4], settings)
        origin_byte     = pt[4:5]
        origin          = origin_byte.decode()
        assembly_header = pt[1029:1030]
        assembly_pt     = pt[1030:]

        if assembly_header == M_S_HEADER:
            depadded     = rm_padding_bytes(assembly_pt)
            decompressed = zlib.decompress(depadded)
            if decompressed[:1] == PRIVATE_MESSAGE_HEADER:
                if window.type == 'group':
                    continue
                decoded = decompressed[1:].decode()

            elif decompressed[:1] == GROUP_MESSAGE_HEADER:

                group_name, decoded = [f.decode() for f in decompressed[9:].split(US_BYTE)]
                if group_name != window.name:
                    continue
                if group_timestamp == decompressed[1:9]:
                    continue
                else:
                    group_timestamp = decompressed[1:9]

            ts_message_list.append((t_stamp, account, origin_byte, decoded))

        elif assembly_header == M_L_HEADER:
            assembly_p_buffer[origin + account] = assembly_pt

        elif assembly_header == M_A_HEADER:
            if (origin + account) in assembly_p_buffer:
                assembly_p_buffer[origin + account] += assembly_pt

        elif assembly_header == M_E_HEADER:
            if (origin + account) in assembly_p_buffer:
                assembly_p_buffer[origin + account] += assembly_pt

                pt_buf       = assembly_p_buffer.pop(origin + account)
                inner_l      = rm_padding_bytes(pt_buf)
                msg_key      = inner_l[-32:]
                enc_msg      = inner_l[:-32]
                decrypted    = auth_and_decrypt(enc_msg, key=msg_key)
                decompressed = zlib.decompress(decrypted)

                if decompressed[:1] == PRIVATE_MESSAGE_HEADER:
                    if window.type == 'group':
                        continue
                    decoded = decompressed[1:].decode()

                elif decompressed[:1] == GROUP_MESSAGE_HEADER:
                    group_name, decoded = [f.decode() for f in decompressed[9:].split(US_BYTE)]
                    if group_name != window.name:
                        continue
                    if group_timestamp == decompressed[1:9]:  # Skip duplicates of outgoing messages
                        continue
                    else:
                        group_timestamp = decompressed[1:9]

                ts_message_list.append((t_stamp, account, origin_byte, decoded))

        elif assembly_header == M_C_HEADER:
            assembly_p_buffer.pop(origin + account, None)

    log_file.close()

    if not export:
        clear_screen()
        print('')

    tty_w  = get_tty_w()

    system = dict(tx="TxM",     rx="RxM",     ut="Unittest")[settings.software_operation]
    m_dir  = dict(tx="sent to", rx="to/from", ut="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.name}", tty_w)

    print(title,       file=f_name)
    print(tty_w * '═', file=f_name)

    for timestamp, account, origin_, message in ts_message_list[-msg_to_load:]:

        nick = "Me" if origin_ == ORIGIN_USER_HEADER else contact_list.get_contact(account).nick

        print(textwrap.fill(f"{timestamp} {nick}:", tty_w), file=f_name)
        print('',                                           file=f_name)
        print(textwrap.fill(message, tty_w),                file=f_name)
        print('',                                           file=f_name)
        print(tty_w * '─',                                  file=f_name)

    if export:
        f_name.close()
    else:
        print('')
示例#29
0
def new_local_key(contact_list: 'ContactList', settings: 'Settings',
                  queues: Dict[bytes, 'Queue'], gateway: 'Gateway') -> None:
    """Run local key agreement protocol.

    Local key encrypts commands and data sent from TxM to RxM. The key is
    delivered to RxM in packet encrypted with an ephemeral symmetric key.
    The checksummed Base58 format decryption key is typed on RxM manually.
    """
    try:
        if contact_list.has_local_contact and settings.session_trickle:
            raise FunctionReturn("Command disabled during trickle connection.")

        clear_screen()
        c_print("Local key setup", head=1, tail=1)

        conf_code = os.urandom(1)
        key = keygen()
        hek = keygen()
        kek = keygen()
        packet = LOCAL_KEY_PACKET_HEADER + encrypt_and_sign(
            key + hek + conf_code, key=kek)

        nh_bypass_msg('start', settings)
        transmit(packet, settings, gateway)

        while True:
            print_kdk(kek, settings)
            purp_code = ask_confirmation_code()
            if purp_code == conf_code.hex():
                print('')
                break
            elif purp_code == 'resend':
                phase("Resending local key", head=2)
                transmit(packet, settings, gateway)
                phase('Done')
                print_on_previous_line(reps=9)
            else:
                box_print([
                    "Incorrect confirmation code. If RxM did not receive",
                    "encrypted local key, resend it by typing 'resend'."
                ],
                          head=1)
                print_on_previous_line(reps=11, delay=2)

        nh_bypass_msg('finish', settings)

        # Add local contact to contact list database
        contact_list.add_contact('local', 'local', 'local', bytes(32),
                                 bytes(32), False, False, False)

        # Add local contact to keyset database
        queues[KEY_MANAGEMENT_QUEUE].put(
            ('ADD', 'local', key, bytes(32), hek, bytes(32)))

        # Notify RxM that confirmation code was successfully entered.
        queue_command(LOCAL_KEY_INSTALLED_HEADER, settings,
                      queues[COMMAND_PACKET_QUEUE])

        box_print(["Successfully added a new local key."])
        clear_screen(delay=1)

    except KeyboardInterrupt:
        raise FunctionReturn("Local key setup aborted.", delay=1)
示例#30
0
文件: tfc.py 项目: barleyj/tfc
def main() -> None:
    """Derive master key, decrypt databases and initialize processes."""
    os.chdir(sys.path[0])
    init_entropy()

    operation, local_test, dd_sockets = process_arguments()

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

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

    if settings.software_operation == 'tx':

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

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

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

    elif settings.software_operation == 'rx':

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

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

    while True:
        try:
            time.sleep(0.1)
            if not all([p.is_alive() for p in process_list]):
                for p in process_list:
                    p.terminate()
                exit()
        except (EOFError, KeyboardInterrupt):
            pass