예제 #1
0
    def test_pub_key_to_short_addr(self):
        self.assertEqual(
            len(
                pub_key_to_short_address(
                    bytes(ONION_SERVICE_PUBLIC_KEY_LENGTH))),
            TRUNC_ADDRESS_LENGTH)

        self.assertIsInstance(
            pub_key_to_short_address(bytes(ONION_SERVICE_PUBLIC_KEY_LENGTH)),
            str)
예제 #2
0
def store_keys_on_removable_drive(
        ct_tag: bytes,  # Encrypted PSK
        salt: bytes,  # Salt for PSK decryption key derivation
        nick: str,  # Contact's nickname
        onion_pub_key: bytes,  # Public key of contact's v3 Onion Service
        onion_service: 'OnionService',  # OnionService object
        settings: 'Settings',  # Settings object
) -> None:
    """Store keys for contact on a removable media."""
    trunc_addr = pub_key_to_short_address(onion_pub_key)
    while True:
        store_d = ask_path_gui(f"Select removable media for {nick}", settings)
        f_name = f"{store_d}/{onion_service.user_short_address}.psk - Give to {trunc_addr}"

        try:
            with open(f_name, "wb+") as f:
                f.write(salt + ct_tag)
                f.flush()
                os.fsync(f.fileno())
            break
        except PermissionError:
            m_print(
                "Error: Did not have permission to write to the directory.",
                delay=0.5)
            continue
예제 #3
0
def contact_rem(onion_pub_key: bytes, ts: 'datetime',
                window_list: 'WindowList', contact_list: 'ContactList',
                group_list: 'GroupList', key_list: 'KeyList',
                settings: 'Settings', master_key: 'MasterKey') -> None:
    """Remove contact from Receiver Program."""
    key_list.remove_keyset(onion_pub_key)
    window_list.remove_window(onion_pub_key)
    short_addr = pub_key_to_short_address(onion_pub_key)

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

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

    contact_list.remove_contact_by_pub_key(onion_pub_key)

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

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

    remove_logs(contact_list, group_list, settings, master_key, onion_pub_key)
예제 #4
0
파일: windows.py 프로젝트: xprog12/tfc
 def update_handle_dict(self, pub_key: bytes) -> None:
     """Update handle for public key in `handle_dict`."""
     if self.contact_list.has_pub_key(pub_key):
         self.handle_dict[pub_key] = self.contact_list.get_nick_by_pub_key(
             pub_key)
     else:
         self.handle_dict[pub_key] = pub_key_to_short_address(pub_key)
예제 #5
0
def ch_nick(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList',
            contact_list: 'ContactList') -> None:
    """Change nickname of contact."""
    onion_pub_key, nick_bytes = separate_header(
        cmd_data, header_length=ONION_SERVICE_PUBLIC_KEY_LENGTH)
    nick = nick_bytes.decode()
    short_addr = pub_key_to_short_address(onion_pub_key)

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

    contact.nick = nick
    contact_list.store_contacts()

    window = window_list.get_window(onion_pub_key)
    window.name = nick
    window.handle_dict[onion_pub_key] = nick

    if window.type == WIN_TYPE_CONTACT:
        window.redraw()

    cmd_win = window_list.get_local_window()
    cmd_win.add_new(ts, f"Changed {short_addr} nick to '{nick}'.", output=True)
예제 #6
0
파일: db_contacts.py 프로젝트: gtog/tfc
    def __init__(self,
                 onion_pub_key:  bytes,
                 nick:           str,
                 tx_fingerprint: bytes,
                 rx_fingerprint: bytes,
                 kex_status:     bytes,
                 log_messages:   bool,
                 file_reception: bool,
                 notifications:  bool
                 ) -> None:
        """Create a new Contact object.

        `self.short_address` is a truncated version of the account used
        to identify TFC account in printed messages.
        """
        self.onion_pub_key   = onion_pub_key
        self.nick            = nick
        self.tx_fingerprint  = tx_fingerprint
        self.rx_fingerprint  = rx_fingerprint
        self.kex_status      = kex_status
        self.log_messages    = log_messages
        self.file_reception  = file_reception
        self.notifications   = notifications
        self.onion_address   = pub_key_to_onion_address(self.onion_pub_key)
        self.short_address   = pub_key_to_short_address(self.onion_pub_key)
        self.tfc_private_key = None  # type: Optional[X448PrivateKey]
예제 #7
0
파일: mock_classes.py 프로젝트: savg110/tfc
    def __init__(self, **kwargs: Any) -> None:
        """Create new OnionService mock object."""
        self.onion_private_key  = ONION_SERVICE_PRIVATE_KEY_LENGTH*b'a'
        self.conf_code          = b'a'
        self.public_key         = bytes(nacl.signing.SigningKey(seed=self.onion_private_key).verify_key)
        self.user_onion_address = pub_key_to_onion_address(self.public_key)
        self.user_short_address = pub_key_to_short_address(self.public_key)
        self.is_delivered       = False

        for key, value in kwargs.items():
            setattr(self, key, value)
예제 #8
0
def client(onion_pub_key: bytes,
           queues: 'QueueDict',
           url_token_private_key: X448PrivateKey,
           tor_port: str,
           gateway: 'Gateway',
           onion_addr_user: str,
           unit_test: bool = False) -> None:
    """Load packets from contact's Onion Service."""
    cached_pk = ''
    short_addr = pub_key_to_short_address(onion_pub_key)
    onion_addr = pub_key_to_onion_address(onion_pub_key)
    check_delay = RELAY_CLIENT_MIN_DELAY
    is_online = False

    session = requests.session()
    session.proxies = {
        'http': f'socks5h://127.0.0.1:{tor_port}',
        'https': f'socks5h://127.0.0.1:{tor_port}'
    }

    rp_print(f"Connecting to {short_addr}...", bold=True)

    # When Transmitter Program sends contact under UNENCRYPTED_ADD_EXISTING_CONTACT, this function
    # receives user's own Onion address: That way it knows to request the contact to add them:
    if onion_addr_user:
        send_contact_request(onion_addr, onion_addr_user, session)

    while True:
        with ignored(EOFError, KeyboardInterrupt, SoftError):
            time.sleep(check_delay)

            url_token_public_key_hex = load_url_token(onion_addr, session)
            is_online, check_delay = manage_contact_status(
                url_token_public_key_hex, check_delay, is_online, short_addr)

            if not is_online:
                continue

            url_token, cached_pk = update_url_token(url_token_private_key,
                                                    url_token_public_key_hex,
                                                    cached_pk, onion_pub_key,
                                                    queues)

            get_data_loop(onion_addr, url_token, short_addr, onion_pub_key,
                          queues, session, gateway)

            if unit_test:
                break
예제 #9
0
파일: db_onion.py 프로젝트: todun/tfc
    def __init__(self, master_key: 'MasterKey') -> None:
        """Create a new OnionService object."""
        self.master_key = master_key
        self.file_name = f'{DIR_USER_DATA}{TX}_onion_db'
        self.is_delivered = False
        self.conf_code = csprng(CONFIRM_CODE_LENGTH)

        ensure_dir(DIR_USER_DATA)
        if os.path.isfile(self.file_name):
            self.onion_private_key = self.load_onion_service_private_key()
        else:
            self.onion_private_key = self.new_onion_service_private_key()
            self.store_onion_service_private_key()

        self.public_key = bytes(
            nacl.signing.SigningKey(seed=self.onion_private_key).verify_key)

        self.user_onion_address = pub_key_to_onion_address(self.public_key)
        self.user_short_address = pub_key_to_short_address(self.public_key)
예제 #10
0
def key_ex_psk_rx(packet: bytes, ts: 'datetime', window_list: 'WindowList',
                  contact_list: 'ContactList', key_list: 'KeyList',
                  settings: 'Settings') -> None:
    """Import Rx-PSK of contact."""
    c_code, onion_pub_key = separate_header(packet, CONFIRM_CODE_LENGTH)
    short_addr = pub_key_to_short_address(onion_pub_key)

    if not contact_list.has_pub_key(onion_pub_key):
        raise FunctionReturn(f"Error: Unknown account '{short_addr}'.",
                             head_clear=True)

    contact = contact_list.get_contact_by_pub_key(onion_pub_key)
    psk_file = ask_path_gui(f"Select PSK for {contact.nick} ({short_addr})",
                            settings,
                            get_file=True)

    try:
        with open(psk_file, 'rb') as f:
            psk_data = f.read()
    except PermissionError:
        raise FunctionReturn("Error: No read permission for the PSK file.")

    if len(psk_data) != PSK_FILE_SIZE:
        raise FunctionReturn("Error: The PSK data in the file was invalid.",
                             head_clear=True)

    salt, ct_tag = separate_header(psk_data, ARGON2_SALT_LENGTH)

    while True:
        try:
            password = MasterKey.get_password("PSK password")
            phase("Deriving the key decryption key", head=2)
            kdk = argon2_kdf(password,
                             salt,
                             time_cost=ARGON2_PSK_TIME_COST,
                             memory_cost=ARGON2_PSK_MEMORY_COST)
            psk = auth_and_decrypt(ct_tag, kdk)
            phase(DONE)
            break

        except nacl.exceptions.CryptoError:
            print_on_previous_line()
            m_print("Invalid password. Try again.", head=1)
            print_on_previous_line(reps=5, delay=1)
        except (EOFError, KeyboardInterrupt):
            raise FunctionReturn("PSK import aborted.",
                                 head=2,
                                 delay=1,
                                 tail_clear=True)

    rx_mk, rx_hk = separate_header(psk, SYMMETRIC_KEY_LENGTH)

    if any(k == bytes(SYMMETRIC_KEY_LENGTH) for k in [rx_mk, rx_hk]):
        raise FunctionReturn("Error: Received invalid keys from contact.",
                             head_clear=True)

    keyset = key_list.get_keyset(onion_pub_key)
    keyset.rx_mk = rx_mk
    keyset.rx_hk = rx_hk
    key_list.store_keys()

    contact.kex_status = KEX_STATUS_HAS_RX_PSK
    contact_list.store_contacts()

    # Pipes protects against shell injection. Source of command's parameter is
    # the program itself, and therefore trusted, but it's still good practice.
    subprocess.Popen(f"shred -n 3 -z -u {pipes.quote(psk_file)}",
                     shell=True).wait()
    if os.path.isfile(psk_file):
        m_print(
            f"Warning! Overwriting of PSK ({psk_file}) failed. Press <Enter> to continue.",
            manual_proceed=True,
            box=True)

    message = f"Added Rx-side PSK for {contact.nick} ({short_addr})."
    local_win = window_list.get_local_window()
    local_win.add_new(ts, message)

    m_print([
        message, '', "Warning!",
        "Physically destroy the keyfile transmission media ",
        "to ensure it does not steal data from this computer!", '',
        f"Confirmation code (to Transmitter): {c_code.hex()}"
    ],
            box=True,
            head=1,
            tail=1)
예제 #11
0
def key_ex_psk_rx(packet: bytes, ts: 'datetime', window_list: 'WindowList',
                  contact_list: 'ContactList', key_list: 'KeyList',
                  settings: 'Settings') -> None:
    """Import Rx-PSK of contact."""
    c_code, onion_pub_key = separate_header(packet, CONFIRM_CODE_LENGTH)
    short_addr = pub_key_to_short_address(onion_pub_key)

    if not contact_list.has_pub_key(onion_pub_key):
        raise SoftError(f"Error: Unknown account '{short_addr}'.",
                        head_clear=True)

    contact = contact_list.get_contact_by_pub_key(onion_pub_key)
    psk_file = ask_path_gui(f"Select PSK for {contact.nick} ({short_addr})",
                            settings,
                            get_file=True)

    try:
        with open(psk_file, 'rb') as f:
            psk_data = f.read()
    except PermissionError:
        raise SoftError("Error: No read permission for the PSK file.")

    if len(psk_data) != PSK_FILE_SIZE:
        raise SoftError("Error: The PSK data in the file was invalid.",
                        head_clear=True)

    salt, ct_tag = separate_header(psk_data, ARGON2_SALT_LENGTH)
    psk = decrypt_rx_psk(ct_tag, salt)
    rx_mk, rx_hk = separate_header(psk, SYMMETRIC_KEY_LENGTH)

    if any(k == bytes(SYMMETRIC_KEY_LENGTH) for k in [rx_mk, rx_hk]):
        raise SoftError("Error: Received invalid keys from contact.",
                        head_clear=True)

    keyset = key_list.get_keyset(onion_pub_key)
    keyset.rx_mk = rx_mk
    keyset.rx_hk = rx_hk
    key_list.store_keys()

    contact.kex_status = KEX_STATUS_HAS_RX_PSK
    contact_list.store_contacts()

    # Pipes protects against shell injection. Source of command's parameter is
    # the program itself, and therefore trusted, but it's still good practice.
    subprocess.Popen(f"shred -n 3 -z -u {pipes.quote(psk_file)}",
                     shell=True).wait()
    if os.path.isfile(psk_file):
        m_print(
            f"Warning! Overwriting of PSK ({psk_file}) failed. Press <Enter> to continue.",
            manual_proceed=True,
            box=True)

    message = f"Added Rx-side PSK for {contact.nick} ({short_addr})."
    cmd_win = window_list.get_command_window()
    cmd_win.add_new(ts, message)

    m_print([
        message, '', "Warning!",
        "Physically destroy the keyfile transmission media ",
        "to ensure it does not steal data from this computer!", '',
        f"Confirmation code (to Transmitter): {c_code.hex()}"
    ],
            box=True,
            head=1,
            tail=1)
예제 #12
0
파일: client.py 프로젝트: gtog/tfc
def client(onion_pub_key: bytes,
           queues: 'QueueDict',
           url_token_private_key: X448PrivateKey,
           tor_port: str,
           gateway: 'Gateway',
           onion_addr_user: str,
           unittest: bool = False) -> None:
    """Load packets from contact's Onion Service."""
    url_token = ''
    cached_pk = ''
    short_addr = pub_key_to_short_address(onion_pub_key)
    onion_addr = pub_key_to_onion_address(onion_pub_key)
    check_delay = RELAY_CLIENT_MIN_DELAY
    is_online = False

    session = requests.session()
    session.proxies = {
        'http': f'socks5h://127.0.0.1:{tor_port}',
        'https': f'socks5h://127.0.0.1:{tor_port}'
    }

    rp_print(f"Connecting to {short_addr}...", bold=True)

    # When Transmitter Program sends contact under UNENCRYPTED_ADD_EXISTING_CONTACT, this function
    # receives user's own Onion address: That way it knows to request the contact to add them:
    if onion_addr_user:
        while True:
            try:
                reply = session.get(
                    f'http://{onion_addr}.onion/contact_request/{onion_addr_user}',
                    timeout=45).text
                if reply == "OK":
                    break
            except requests.exceptions.RequestException:
                time.sleep(RELAY_CLIENT_MIN_DELAY)

    while True:
        with ignored(EOFError, KeyboardInterrupt):
            time.sleep(check_delay)

            # Obtain URL token
            # ----------------

            # Load URL token public key from contact's Onion Service root domain
            try:
                url_token_public_key_hex = session.get(
                    f'http://{onion_addr}.onion/', timeout=45).text
            except requests.exceptions.RequestException:
                url_token_public_key_hex = ''

            # Manage online status of contact based on availability of URL token's public key
            if url_token_public_key_hex == '':
                if check_delay < RELAY_CLIENT_MAX_DELAY:
                    check_delay *= 2
                if check_delay > CLIENT_OFFLINE_THRESHOLD and is_online:
                    is_online = False
                    rp_print(f"{short_addr} is now offline", bold=True)
                continue
            else:
                check_delay = RELAY_CLIENT_MIN_DELAY
                if not is_online:
                    is_online = True
                    rp_print(f"{short_addr} is now online", bold=True)

            # When contact's URL token public key changes, update URL token
            if url_token_public_key_hex != cached_pk:
                try:
                    public_key = bytes.fromhex(url_token_public_key_hex)

                    if len(public_key
                           ) != TFC_PUBLIC_KEY_LENGTH or public_key == bytes(
                               TFC_PUBLIC_KEY_LENGTH):
                        raise ValueError

                    shared_secret = url_token_private_key.exchange(
                        X448PublicKey.from_public_bytes(public_key))
                    url_token = hashlib.blake2b(
                        shared_secret,
                        digest_size=SYMMETRIC_KEY_LENGTH).hexdigest()
                except (TypeError, ValueError):
                    continue

                cached_pk = url_token_public_key_hex  # Update client's URL token public key
                queues[URL_TOKEN_QUEUE].put(
                    (onion_pub_key,
                     url_token))  # Update Flask server's URL token for contact

            # Load TFC data with URL token
            # ----------------------------

            get_data_loop(onion_addr, url_token, short_addr, onion_pub_key,
                          queues, session, gateway)

            if unittest:
                break
예제 #13
0
파일: key_exchanges.py 프로젝트: gtog/tfc
def create_pre_shared_key(
    onion_pub_key: bytes,  # Public key of contact's v3 Onion Service
    nick: str,  # Nick of contact
    contact_list: 'ContactList',  # Contact list object
    settings: 'Settings',  # Settings object
    onion_service: 'OnionService',  # OnionService object
    queues: 'QueueDict'  # Dictionary of multiprocessing queues
) -> None:
    """Generate a new pre-shared key for manual key delivery.

    Pre-shared keys offer a low-tech solution against the slowly
    emerging threat of quantum computers. PSKs are less convenient and
    not usable in every scenario, but until a quantum-safe key exchange
    algorithm with reasonably short keys is standardized, TFC can't
    provide a better alternative against quantum computers.

    The generated keys are protected by a key encryption key, derived
    from a 256-bit salt and a password (that is to be shared with the
    recipient) using Argon2d key derivation function.

    The encrypted message and header keys are stored together with salt
    on a removable media. This media must be a never-before-used device
    from sealed packaging. Re-using an old device might infect Source
    Computer, and the malware could either copy sensitive data on that
    removable media, or Source Computer might start transmitting the
    sensitive data covertly over the serial interface to malware on
    Networked Computer.

    Once the key has been exported to the clean drive, contact data and
    keys are exported to the Receiver Program on Destination computer.
    The transmission is encrypted with the local key.
    """
    try:
        tx_mk = csprng()
        tx_hk = csprng()
        salt = csprng()

        password = MasterKey.new_password("password for PSK")

        phase("Deriving key encryption key", head=2)
        kek = argon2_kdf(password,
                         salt,
                         time_cost=ARGON2_PSK_TIME_COST,
                         memory_cost=ARGON2_PSK_MEMORY_COST)
        phase(DONE)

        ct_tag = encrypt_and_sign(tx_mk + tx_hk, key=kek)

        while True:
            trunc_addr = pub_key_to_short_address(onion_pub_key)
            store_d = ask_path_gui(f"Select removable media for {nick}",
                                   settings)
            f_name = f"{store_d}/{onion_service.user_short_address}.psk - Give to {trunc_addr}"
            try:
                with open(f_name, 'wb+') as f:
                    f.write(salt + ct_tag)
                break
            except PermissionError:
                m_print(
                    "Error: Did not have permission to write to the directory.",
                    delay=0.5)
                continue

        command = (KEY_EX_PSK_TX + onion_pub_key + tx_mk + csprng() + tx_hk +
                   csprng() + str_to_bytes(nick))

        queue_command(command, settings, queues)

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

        queues[KEY_MANAGEMENT_QUEUE].put(
            (KDB_ADD_ENTRY_HEADER, onion_pub_key, tx_mk, csprng(), tx_hk,
             csprng()))

        m_print(f"Successfully added {nick}.",
                bold=True,
                tail_clear=True,
                delay=1,
                head=1)

    except (EOFError, KeyboardInterrupt):
        raise FunctionReturn("PSK generation aborted.",
                             tail_clear=True,
                             delay=1,
                             head=2)
예제 #14
0
def remove_logs(contact_list: 'ContactList', group_list: 'GroupList',
                settings: 'Settings', master_key: 'MasterKey',
                selector: bytes) -> None:
    """\
    Remove log entries for selector (public key of an account/group ID).

    If the selector is a public key, all messages (both the private
    conversation and any associated group messages) sent to and received
    from the associated contact are removed. If the selector is a group
    ID, only messages for the group matching that group ID are removed.
    """
    ensure_dir(DIR_USER_DATA)
    file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
    temp_name = file_name + TEMP_SUFFIX
    packet_list = PacketList(settings, contact_list)
    entries_to_keep = []  # type: List[bytes]
    removed = False
    contact = len(selector) == ONION_SERVICE_PUBLIC_KEY_LENGTH

    check_log_file_exists(file_name)
    message_log = MessageLog(file_name, master_key.master_key)

    for log_entry in message_log:

        onion_pub_key, _, origin, assembly_packet = separate_headers(
            log_entry, [
                ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH,
                ORIGIN_HEADER_LENGTH
            ])
        if contact:
            if onion_pub_key == selector:
                removed = True
            else:
                entries_to_keep.append(log_entry)

        else:  # Group
            packet = packet_list.get_packet(onion_pub_key,
                                            origin,
                                            MESSAGE,
                                            log_access=True)
            try:
                packet.add_packet(assembly_packet, log_entry)
            except SoftError:
                continue
            if not packet.is_complete:
                continue

            removed = check_packet_fate(entries_to_keep, packet, removed,
                                        selector)

    message_log.close_database()

    message_log_temp = MessageLog(temp_name, master_key.master_key)

    for log_entry in entries_to_keep:
        message_log_temp.insert_log_entry(log_entry)
    message_log_temp.close_database()

    os.replace(temp_name, file_name)

    try:
        name = contact_list.get_nick_by_pub_key(
            selector) if contact else group_list.get_group_by_id(selector).name
    except StopIteration:
        name = pub_key_to_short_address(selector) if contact else b58encode(
            selector)

    action = "Removed" if removed else "Found no"
    win_type = "contact" if contact else "group"

    raise SoftError(f"{action} log entries for {win_type} '{name}'.")
예제 #15
0
def remove_logs(contact_list: 'ContactList', group_list: 'GroupList',
                settings: 'Settings', master_key: 'MasterKey',
                selector: bytes) -> None:
    """\
    Remove log entries for selector (public key of an account/group ID).

    If the selector is a public key, all messages (both the private
    conversation and any associated group messages) sent to and received
    from the associated contact are removed. If the selector is a group
    ID, only messages for group determined by that group ID are removed.
    """
    ensure_dir(DIR_USER_DATA)
    file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs'
    temp_name = f'{file_name}_temp'
    log_file = get_logfile(file_name)
    packet_list = PacketList(settings, contact_list)
    ct_to_keep = []  # type: List[bytes]
    removed = False
    contact = len(selector) == ONION_SERVICE_PUBLIC_KEY_LENGTH

    for ct in iter(lambda: log_file.read(LOG_ENTRY_LENGTH), b''):
        plaintext = auth_and_decrypt(ct,
                                     master_key.master_key,
                                     database=file_name)

        onion_pub_key, _, origin, assembly_packet = separate_headers(
            plaintext, [
                ONION_SERVICE_PUBLIC_KEY_LENGTH, TIMESTAMP_LENGTH,
                ORIGIN_HEADER_LENGTH
            ])
        if contact:
            if onion_pub_key == selector:
                removed = True
            else:
                ct_to_keep.append(ct)

        else:  # Group
            packet = packet_list.get_packet(onion_pub_key,
                                            origin,
                                            MESSAGE,
                                            log_access=True)
            try:
                packet.add_packet(assembly_packet, ct)
            except FunctionReturn:
                continue
            if not packet.is_complete:
                continue

            _, header, message = separate_headers(
                packet.assemble_message_packet(),
                [WHISPER_FIELD_LENGTH, MESSAGE_HEADER_LENGTH])

            if header == PRIVATE_MESSAGE_HEADER:
                ct_to_keep.extend(packet.log_ct_list)
                packet.clear_assembly_packets()

            elif header == GROUP_MESSAGE_HEADER:
                group_id, _ = separate_header(message, GROUP_ID_LENGTH)
                if group_id == selector:
                    removed = True
                else:
                    ct_to_keep.extend(packet.log_ct_list)
                    packet.clear_assembly_packets()

    log_file.close()

    if os.path.isfile(temp_name):
        os.remove(temp_name)

    with open(temp_name, 'wb+') as f:
        if ct_to_keep:
            f.write(b''.join(ct_to_keep))

    os.remove(file_name)
    os.rename(temp_name, file_name)

    try:
        name = contact_list.get_contact_by_pub_key(selector).nick \
            if contact else group_list.get_group_by_id(selector).name
    except StopIteration:
        name = pub_key_to_short_address(selector) \
            if contact else b58encode(selector)

    action = "Removed" if removed else "Found no"
    win_type = "contact" if contact else "group"

    raise FunctionReturn(f"{action} log entries for {win_type} '{name}'.")