コード例 #1
0
ファイル: test_input.py プロジェクト: xprog12/tfc
    def test_get_b58_pub_key(self, *_: Any) -> None:
        for local_testing in [True, False]:
            self.settings.local_testing_mode = local_testing
            key = get_b58_key(B58_PUBLIC_KEY, self.settings)
            self.assertIsInstance(key, bytes)
            self.assertEqual(len(key), TFC_PUBLIC_KEY_LENGTH)

            with self.assertRaises(ValueError):
                get_b58_key(B58_PUBLIC_KEY, self.settings,
                            nick_to_short_address('Alice'))
コード例 #2
0
def decrypt_local_key(ts: 'datetime', packet: bytes, kdk_hashes: List[bytes],
                      packet_hashes: List[bytes], settings: 'Settings',
                      l_queue: 'local_key_queue') -> Tuple['datetime', bytes]:
    """Decrypt local key packet."""
    while True:
        kdk = get_b58_key(B58_LOCAL_KEY, settings)
        kdk_hash = blake2b(kdk)

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

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

        protect_kdk(kdk)

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

        return ts, plaintext
コード例 #3
0
ファイル: key_exchanges.py プロジェクト: barleyj/tfc
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
ファイル: test_input.py プロジェクト: barleyj/tfc
    def test_get_b58_key(self):

        for kt in ['pubkey', 'localkey', 'imported_file']:
            input_list = [
                'bad', "2QJL5gVSPEjMTaxWPfYkzG9UJxzZDNSx6PPeVWdzS5CFN7knZa",
                "2QJL5gVSPEjMTaxWPfYkzG9UJxzZDNSx6PPeVWdzS5CFN7knZy"
            ]

            gen = iter(input_list)

            def mock_input(_):
                return str(next(gen))

            builtins.input = mock_input
            key = get_b58_key(kt)
            self.assertIsInstance(key, bytes)
            self.assertEqual(len(key), 32)

        with self.assertRaises(SystemExit):
            get_b58_key('invalid_keytype')
コード例 #5
0
ファイル: key_exchanges.py プロジェクト: AJMartel/tfc
def process_local_key(ts: 'datetime', packet: bytes, window_list: 'WindowList',
                      contact_list: 'ContactList', key_list: 'KeyList',
                      settings: 'Settings') -> None:
    """Decrypt local key packet and add local contact/keyset."""
    bootstrap = not key_list.has_local_key()

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

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

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

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

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

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

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

        if bootstrap:
            window_list.active_win = local_win

    except KeyboardInterrupt:
        raise FunctionReturn("Local key setup aborted.",
                             delay=1,
                             head=3,
                             tail_clear=True)
コード例 #6
0
def process_imported_file(ts: 'datetime', packet: bytes,
                          window_list: 'WindowList', settings: 'Settings'):
    """Decrypt and store imported file."""
    while True:
        try:
            print('')
            key = get_b58_key(B58_FILE_KEY, settings)
        except KeyboardInterrupt:
            raise FunctionReturn("File import aborted.", head=2)

        try:
            phase("Decrypting file", head=1)
            file_pt = auth_and_decrypt(packet[1:], key, soft_e=True)
            phase(DONE)
            break
        except (nacl.exceptions.CryptoError, nacl.exceptions.ValueError):
            phase('ERROR', done=True)
            c_print("Invalid decryption key. Try again.")
            print_on_previous_line(reps=7, delay=1.5)
        except KeyboardInterrupt:
            phase('ABORT', done=True)
            raise FunctionReturn("File import aborted.")

    try:
        phase("Decompressing file")
        file_dc = zlib.decompress(file_pt)
        phase(DONE)
    except zlib.error:
        phase('ERROR', done=True)
        raise FunctionReturn("Error: Decompression of file data failed.")

    try:
        f_name = bytes_to_str(file_dc[:PADDED_UTF32_STR_LEN])
    except UnicodeError:
        raise FunctionReturn("Error: Received file name had invalid encoding.")

    if not f_name.isprintable() or not f_name:
        raise FunctionReturn("Error: Received file had an invalid name.")

    f_data = file_dc[PADDED_UTF32_STR_LEN:]
    final_name = store_unique(f_data, DIR_IMPORTED, f_name)

    message = f"Stored imported file as '{final_name}'"
    box_print(message, head=1)

    local_win = window_list.get_local_window()
    local_win.add_new(ts, message)
コード例 #7
0
ファイル: key_exchanges.py プロジェクト: savg110/tfc
def exchange_public_keys(
    onion_pub_key: bytes,
    tfc_public_key_user: bytes,
    kdk_hash: bytes,
    contact: 'Contact',
    settings: 'Settings',
    queues: 'QueueDict',
) -> bytes:
    """Exchange public keys with contact.

    This function outputs the user's public key and waits for user to
    enter the public key of the contact. If the User presses <Enter>,
    the function will resend the users' public key to contact.
    """
    public_key_packet = PUBLIC_KEY_DATAGRAM_HEADER + onion_pub_key + tfc_public_key_user
    queue_to_nc(public_key_packet, queues[RELAY_PACKET_QUEUE])

    while True:
        try:
            tfc_public_key_contact = get_b58_key(B58_PUBLIC_KEY, settings,
                                                 contact.short_address)
        except ValueError as invalid_pub_key:
            invalid_key = str(invalid_pub_key).encode()

            # Do not send packet to Relay Program if the user has for some reason
            # managed to embed the local key decryption key inside the public key.
            substrings = split_to_substrings(invalid_key,
                                             ENCODED_B58_KDK_LENGTH)
            safe_string = not any(
                blake2b(substring) == kdk_hash for substring in substrings)

            if safe_string:
                public_key_packet = (UNENCRYPTED_DATAGRAM_HEADER +
                                     UNENCRYPTED_PUBKEY_CHECK + onion_pub_key +
                                     invalid_key)
                queue_to_nc(public_key_packet, queues[RELAY_PACKET_QUEUE])
            continue

        if tfc_public_key_contact == b'':
            public_key_packet = PUBLIC_KEY_DATAGRAM_HEADER + onion_pub_key + tfc_public_key_user
            queue_to_nc(public_key_packet, queues[RELAY_PACKET_QUEUE])
            continue

        return tfc_public_key_contact
コード例 #8
0
def process_imported_file(ts:          'datetime',
                          packet:      bytes,
                          window_list: 'WindowList'):
    """Decrypt and store imported file."""
    while True:
        try:
            print('')
            key = get_b58_key('imported_file')
            phase("Decrypting file", head=1)
            file_pt = auth_and_decrypt(packet[1:], key, soft_e=True)
            phase("Done")
            break
        except nacl.exceptions.CryptoError:
            c_print("Invalid decryption key. Try again.", head=2)
            print_on_previous_line(reps=6, delay=1.5)
        except KeyboardInterrupt:
            raise FunctionReturn("File import aborted.")

    try:
        phase("Decompressing file")
        file_dc = zlib.decompress(file_pt)
        phase("Done")
    except zlib.error:
        raise FunctionReturn("Decompression of file data failed.")

    try:
        f_name  = bytes_to_str(file_dc[:1024])
    except UnicodeError:
        raise FunctionReturn("Received file had an invalid name.")

    if not f_name.isprintable():
        raise FunctionReturn("Received file had an invalid name.")

    f_data     = file_dc[1024:]
    final_name = store_unique(f_data, DIR_IMPORTED, f_name)

    message = "Stored imported file to {}/{}".format(DIR_IMPORTED, final_name)
    box_print(message, head=1)

    local_win = window_list.get_local_window()
    local_win.print_new(ts, message, print_=False)
コード例 #9
0
    def test_get_b58_key(self, *_):
        for boolean in [True, False]:
            self.settings.local_testing_mode = boolean
            key = get_b58_key(B58_LOCAL_KEY, self.settings)

            self.assertIsInstance(key, bytes)
            self.assertEqual(len(key), SYMMETRIC_KEY_LENGTH)

            with self.assertRaises(SystemExit):
                get_b58_key('invalid_key_type', self.settings)

        for boolean in [True, False]:
            self.settings.local_testing_mode = boolean
            key = get_b58_key(B58_PUBLIC_KEY, self.settings, nick_to_short_address('Alice'))

            self.assertIsInstance(key, bytes)
            self.assertEqual(len(key), TFC_PUBLIC_KEY_LENGTH)

            with self.assertRaises(SystemExit):
                get_b58_key('invalid_key_type', self.settings)
コード例 #10
0
    def test_get_b58_key(self):
        for boolean in [True, False]:
            self.settings.local_testing_mode = boolean
            for key_type in [B58_PUB_KEY, B58_LOCAL_KEY]:
                input_list = [
                    "bad",
                    "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTa",
                    "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"
                ]
                gen = iter(input_list)
                builtins.input = lambda _: str(next(gen))
                key = get_b58_key(key_type, self.settings)

                self.assertIsInstance(key, bytes)
                self.assertEqual(len(key), KEY_LENGTH)

            with self.assertRaises(SystemExit):
                get_b58_key('invalid_keytype', self.settings)

        for boolean in [True, False]:
            self.settings.local_testing_mode = boolean
            for key_type in [B58_FILE_KEY]:
                input_list = [
                    "bad",
                    "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwi1C2Ga",
                    "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwi1C2GD"
                ]
                gen = iter(input_list)
                builtins.input = lambda _: str(next(gen))
                key = get_b58_key(key_type, self.settings)

                self.assertIsInstance(key, bytes)
                self.assertEqual(len(key), KEY_LENGTH)

            with self.assertRaises(SystemExit):
                get_b58_key('invalid_keytype', self.settings)
コード例 #11
0
ファイル: key_exchanges.py プロジェクト: AJMartel/tfc
def start_key_exchange(account:      str,
                       user:         str,
                       nick:         str,
                       contact_list: 'ContactList',
                       settings:     'Settings',
                       queues:       Dict[bytes, 'Queue']) -> 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 = X25519 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
    :return:             None
    """
    try:
        tx_sk = nacl.public.PrivateKey(csprng())
        tx_pk = bytes(tx_sk.public_key)

        while True:
            queue_to_nh(PUBLIC_KEY_PACKET_HEADER
                        + tx_pk
                        + user.encode()
                        + US_BYTE
                        + account.encode(),
                        settings, queues[NH_PACKET_QUEUE])

            rx_pk = get_b58_key(B58_PUB_KEY, settings)

            if rx_pk != RESEND.encode():
                break

        if rx_pk == bytes(KEY_LENGTH):
            # Public key is zero with negligible probability, therefore we
            # assume such key is malicious and attempts to either result in
            # zero shared key (pointless considering implementation), or to
            # DoS the key exchange as libsodium does not accept zero keys.
            box_print(["Warning!",
                       "Received a malicious public key from network.",
                       "Aborting key exchange for your safety."], tail=1)
            raise FunctionReturn("Error: Zero public key", output=False)

        dh_box = nacl.public.Box(tx_sk, nacl.public.PublicKey(rx_pk))
        dh_ssk = dh_box.shared_key()

        # Domain separate each key with key-type specific context variable
        # 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 de-anonymization of IM accounts in cases where
        # clients connect to the compromised server via Tor. The preimage
        # resistance of hash chain protects the shared secret from leaking.
        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(["Warning!",
                       "Possible man-in-the-middle attack detected.",
                       "Aborting key exchange for your safety."], tail=1)
            raise FunctionReturn("Error: Fingerprint mismatch", output=False)

        packet = KEY_EX_X25519_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_messages_by_default,
                                 settings.accept_files_by_default,
                                 settings.show_notifications_by_default)

        # Use random values as Rx-keys to prevent decryption if they're accidentally used.
        queues[KEY_MANAGEMENT_QUEUE].put((KDB_ADD_ENTRY_HEADER, account,
                                          tx_key, csprng(),
                                          tx_hek, csprng()))

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

    except KeyboardInterrupt:
        raise FunctionReturn("Key exchange aborted.", delay=1, head=2, tail_clear=True)
コード例 #12
0
ファイル: test_input.py プロジェクト: xprog12/tfc
 def test_invalid_key_type_raises_critical_error(self) -> None:
     with self.assertRaises(SystemExit):
         get_b58_key('invalid_key_type', self.settings)
コード例 #13
0
 def test_empty_pub_key_returns_empty_bytes(self, *_):
     key = get_b58_key(B58_PUBLIC_KEY, self.settings)
     self.assertEqual(key, b'')
コード例 #14
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)
コード例 #15
0
def process_local_key(ts: 'datetime', packet: bytes, window_list: 'WindowList',
                      contact_list: 'ContactList', key_list: 'KeyList',
                      settings: 'Settings', kdk_hashes: List[bytes],
                      packet_hashes: List[bytes], l_queue: 'Queue') -> None:
    """Decrypt local key packet and add local contact/keyset."""
    bootstrap = not key_list.has_local_keyset()
    plaintext = None

    try:
        packet_hash = blake2b(packet)

        # Check if the packet is an old one
        if packet_hash in packet_hashes:
            raise FunctionReturn("Error: Received old local key packet.",
                                 output=False)

        while True:
            m_print("Local key setup",
                    bold=True,
                    head_clear=True,
                    head=1,
                    tail=1)
            kdk = get_b58_key(B58_LOCAL_KEY, settings)
            kdk_hash = blake2b(kdk)

            try:
                plaintext = auth_and_decrypt(packet, kdk)
                break
            except nacl.exceptions.CryptoError:
                # Check if key was an old one
                if kdk_hash in kdk_hashes:
                    m_print("Error: Entered an old local key decryption key.",
                            delay=1)
                    continue

                # Check if the kdk was for a packet further ahead in the queue
                buffer = []  # type: List[Tuple[datetime, bytes]]
                while l_queue.qsize() > 0:
                    tup = l_queue.get()  # type: Tuple[datetime, bytes]
                    if tup not in buffer:
                        buffer.append(tup)

                for i, tup in enumerate(buffer):
                    try:
                        plaintext = auth_and_decrypt(tup[1], kdk)

                        # If we reach this point, decryption was successful.
                        for unexamined in buffer[i + 1:]:
                            l_queue.put(unexamined)
                        buffer = []
                        ts = tup[0]
                        break

                    except nacl.exceptions.CryptoError:
                        continue
                else:
                    # Finished the buffer without finding local key CT
                    # for the kdk. Maybe the kdk is from another session.
                    raise FunctionReturn(
                        "Error: Incorrect key decryption key.", delay=1)

            break

        # This catches PyCharm's weird claim that plaintext might be referenced before assignment
        if plaintext is None:  # pragma: no cover
            raise FunctionReturn("Error: Could not decrypt local key.")

        # Add local contact to contact list database
        contact_list.add_contact(LOCAL_PUBKEY,
                                 LOCAL_NICK, KEX_STATUS_LOCAL_KEY,
                                 bytes(FINGERPRINT_LENGTH),
                                 bytes(FINGERPRINT_LENGTH), False, False, True)

        tx_mk, tx_hk, c_code = separate_headers(plaintext,
                                                2 * [SYMMETRIC_KEY_LENGTH])

        # Add local keyset to keyset database
        key_list.add_keyset(onion_pub_key=LOCAL_PUBKEY,
                            tx_mk=tx_mk,
                            rx_mk=csprng(),
                            tx_hk=tx_hk,
                            rx_hk=csprng())

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

        # Prevent leak of KDK via terminal history / clipboard
        readline.clear_history()
        os.system(RESET)
        root = tkinter.Tk()
        root.withdraw()
        try:
            if root.clipboard_get() == b58encode(kdk):
                root.clipboard_clear()
        except tkinter.TclError:
            pass
        root.destroy()

        m_print([
            "Local key successfully installed.",
            f"Confirmation code (to Transmitter): {c_code.hex()}"
        ],
                box=True,
                head=1)

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

        if bootstrap:
            window_list.active_win = local_win

    except (EOFError, KeyboardInterrupt):
        m_print("Local key setup aborted.",
                bold=True,
                tail_clear=True,
                delay=1,
                head=2)

        if window_list.active_win is not None and not bootstrap:
            window_list.active_win.redraw()

        raise FunctionReturn("Local key setup aborted.", output=False)
コード例 #16
0
ファイル: key_exchanges.py プロジェクト: gtog/tfc
def start_key_exchange(
    onion_pub_key: bytes,  # Public key of contact's v3 Onion Service
    nick: str,  # Contact's nickname
    contact_list: 'ContactList',  # Contact list object
    settings: 'Settings',  # Settings object
    queues: 'QueueDict'  # Dictionary of multiprocessing queues
) -> None:
    """Start X448 key exchange with the recipient.

    This function first creates the X448 key pair. It then outputs the
    public key to Relay Program on Networked Computer, that passes the
    public key to contact's Relay Program. When contact's public key
    reaches the user's Relay Program, the user will manually copy the
    key into their Transmitter Program.

    The X448 shared secret is used to create unidirectional message and
    header keys, that will be used in forward secret communication. This
    is followed by the fingerprint verification where the user manually
    authenticates the public key.

    Once the fingerprint has been accepted, this function will add the
    contact/key data to contact/key databases, and export that data to
    the Receiver Program on Destination Computer. The transmission is
    encrypted with the local key.

    ---

    TFC provides proactive security by making fingerprint verification
    part of the key exchange. This prevents the situation where the
    users don't know about the feature, and thus helps minimize the risk
    of MITM attack.

    The fingerprints can be skipped by pressing Ctrl+C. This feature is
    not advertised however, because verifying fingerprints the only
    strong way to be sure TFC is not under MITM attack. When
    verification is skipped, TFC marks the contact's X448 keys as
    "Unverified". The fingerprints can later be verified with the
    `/verify` command: answering `yes` to the question on whether the
    fingerprints match, marks the X448 keys as "Verified".

    Variable naming:
        tx = user's key     rx = contact's key    fp = fingerprint
        mk = message key    hk = header key
    """
    if not contact_list.has_pub_key(onion_pub_key):
        contact_list.add_contact(onion_pub_key, nick,
                                 bytes(FINGERPRINT_LENGTH),
                                 bytes(FINGERPRINT_LENGTH), KEX_STATUS_PENDING,
                                 settings.log_messages_by_default,
                                 settings.accept_files_by_default,
                                 settings.show_notifications_by_default)
    contact = contact_list.get_contact_by_pub_key(onion_pub_key)

    # Generate new private key or load cached private key
    if contact.tfc_private_key is None:
        tfc_private_key_user = X448.generate_private_key()
    else:
        tfc_private_key_user = contact.tfc_private_key

    try:
        tfc_public_key_user = X448.derive_public_key(tfc_private_key_user)

        # Import public key of contact
        while True:
            public_key_packet = PUBLIC_KEY_DATAGRAM_HEADER + onion_pub_key + tfc_public_key_user
            queue_to_nc(public_key_packet, queues[RELAY_PACKET_QUEUE])

            tfc_public_key_contact = get_b58_key(B58_PUBLIC_KEY, settings,
                                                 contact.short_address)
            if tfc_public_key_contact != b'':
                break

        # Validate public key of contact
        if len(tfc_public_key_contact) != TFC_PUBLIC_KEY_LENGTH:
            m_print([
                "Warning!", "Received invalid size public key.",
                "Aborting key exchange for your safety."
            ],
                    bold=True,
                    tail=1)
            raise FunctionReturn("Error: Invalid public key length",
                                 output=False)

        if tfc_public_key_contact == bytes(TFC_PUBLIC_KEY_LENGTH):
            # The public key of contact is zero with negligible probability,
            # therefore we assume such key is malicious and attempts to set
            # the shared key to zero.
            m_print([
                "Warning!", "Received a malicious zero-public key.",
                "Aborting key exchange for your safety."
            ],
                    bold=True,
                    tail=1)
            raise FunctionReturn("Error: Zero public key", output=False)

        # Derive shared key
        dh_shared_key = X448.shared_key(tfc_private_key_user,
                                        tfc_public_key_contact)

        # Domain separate unidirectional keys from shared key by using public
        # keys as message and the context variable as personalization string.
        tx_mk = blake2b(tfc_public_key_contact,
                        dh_shared_key,
                        person=b'message_key',
                        digest_size=SYMMETRIC_KEY_LENGTH)
        rx_mk = blake2b(tfc_public_key_user,
                        dh_shared_key,
                        person=b'message_key',
                        digest_size=SYMMETRIC_KEY_LENGTH)
        tx_hk = blake2b(tfc_public_key_contact,
                        dh_shared_key,
                        person=b'header_key',
                        digest_size=SYMMETRIC_KEY_LENGTH)
        rx_hk = blake2b(tfc_public_key_user,
                        dh_shared_key,
                        person=b'header_key',
                        digest_size=SYMMETRIC_KEY_LENGTH)

        # Domain separate fingerprints of public keys by using the
        # shared secret as key and the context variable as
        # personalization string. This way entities who might monitor
        # fingerprint verification channel are unable to correlate
        # spoken values with public keys that they might see on RAM or
        # screen of Networked Computer: Public keys can not be derived
        # from the fingerprints due to preimage resistance of BLAKE2b,
        # and fingerprints can not be derived from public key without
        # the X448 shared secret. Using the context variable ensures
        # fingerprints are distinct from derived message and header keys.
        tx_fp = blake2b(tfc_public_key_user,
                        dh_shared_key,
                        person=b'fingerprint',
                        digest_size=FINGERPRINT_LENGTH)
        rx_fp = blake2b(tfc_public_key_contact,
                        dh_shared_key,
                        person=b'fingerprint',
                        digest_size=FINGERPRINT_LENGTH)

        # Verify fingerprints
        try:
            if not verify_fingerprints(tx_fp, rx_fp):
                m_print([
                    "Warning!", "Possible man-in-the-middle attack detected.",
                    "Aborting key exchange for your safety."
                ],
                        bold=True,
                        tail=1)
                raise FunctionReturn("Error: Fingerprint mismatch",
                                     delay=2.5,
                                     output=False)
            kex_status = KEX_STATUS_VERIFIED

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

        # Send keys to the Receiver Program
        c_code = blake2b(onion_pub_key, digest_size=CONFIRM_CODE_LENGTH)
        command = (KEY_EX_ECDHE + 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)

        # Store contact data into databases
        contact.tfc_private_key = None
        contact.tx_fingerprint = tx_fp
        contact.rx_fingerprint = rx_fp
        contact.kex_status = kex_status
        contact_list.store_contacts()

        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):
        contact.tfc_private_key = tfc_private_key_user
        raise FunctionReturn("Key exchange interrupted.",
                             tail_clear=True,
                             delay=1,
                             head=2)
コード例 #17
0
ファイル: test_input.py プロジェクト: xprog12/tfc
 def test_get_b58_local_key(self, *_: Any) -> None:
     key = get_b58_key(B58_LOCAL_KEY, self.settings)
     self.assertIsInstance(key, bytes)
     self.assertEqual(len(key), SYMMETRIC_KEY_LENGTH)