示例#1
0
def decrypt_assembly_packet(packet: bytes, window_list: 'WindowList',
                            contact_list: 'ContactList',
                            key_list: 'KeyList') -> Tuple[bytes, str, bytes]:
    """Decrypt assembly packet from contact/local TxM."""
    enc_harac = packet[1:49]
    enc_msg = packet[49:345]
    window = window_list.get_local_window()

    origin, direction, key_dir, p_type, account, nick = get_packet_values(
        packet, window, contact_list)

    # Load keys
    keyset = key_list.get_keyset(account)
    header_key = getattr(keyset, f'{key_dir}_hek')
    message_key = getattr(keyset, f'{key_dir}_key')

    if any(k == bytes(KEY_LENGTH) for k in [header_key, message_key]):
        raise FunctionReturn("Warning! Loaded zero-key for packet decryption.")

    # Decrypt hash ratchet counter
    try:
        harac_bytes = auth_and_decrypt(enc_harac, header_key, soft_e=True)
    except nacl.exceptions.CryptoError:
        raise FunctionReturn(
            f"Warning! Received {p_type} {direction} {nick} had an invalid hash ratchet MAC.",
            window=window)

    # Catch up with hash ratchet offset
    purp_harac = bytes_to_int(harac_bytes)
    stored_harac = getattr(keyset, f'{key_dir}_harac')
    offset = purp_harac - stored_harac
    if offset < 0:
        raise FunctionReturn(
            f"Warning! Received {p_type} {direction} {nick} had an expired hash ratchet counter.",
            window=window)

    process_offset(offset, origin, direction, nick, window)
    for _ in range(offset):
        message_key = hash_chain(message_key)

    # Decrypt packet
    try:
        assembly_packet = auth_and_decrypt(enc_msg, message_key, soft_e=True)
    except nacl.exceptions.CryptoError:
        raise FunctionReturn(
            f"Warning! Received {p_type} {direction} {nick} had an invalid MAC.",
            window=window)

    # Update keys in database
    keyset.update_key(key_dir, hash_chain(message_key), offset + 1)

    return assembly_packet, account, origin
示例#2
0
文件: encoding.py 项目: barleyj/tfc
def b58decode(string: str) -> bytes:
    """Decode a Base58-encoded string and verify checksum."""
    b58_alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

    orig_len = len(string)
    string = string.lstrip('1')
    new_len = len(string)

    p, acc = 1, 0
    for c in string[::-1]:
        acc += p * b58_alphabet.index(c)
        p *= 58

    decoded = []
    while acc > 0:
        acc, mod = divmod(acc, 256)
        decoded.append(mod)

    decoded_ = (
        bytes(decoded) +
        (orig_len - new_len) * b'\x00')[::-1]  # type: Union[bytes, List[int]]

    if hash_chain(bytes(decoded_[:-4]))[:4] != decoded_[-4:]:
        raise ValueError

    return bytes(decoded_[:-4])
示例#3
0
文件: packet.py 项目: barleyj/tfc
def queue_command(payload:  bytes,
                  settings: 'Settings',
                  c_queue:  'Queue') -> None:
    """Split command into assembly packets and queue them.

    :param payload:  Command's plaintext string.
    :param settings: Settings object
    :param c_queue:  Multiprocessing queue for commands
    :return:         None
    """
    payload = zlib.compress(payload, level=9)

    if len(payload) < 255:
        padded      = byte_padding(payload)
        packet_list = [C_S_HEADER + padded]
    else:
        payload += hash_chain(payload)
        padded   = byte_padding(payload)
        p_list   = split_byte_string(padded, item_len=255)

        packet_list = ([C_L_HEADER + p_list[0]] +
                       [C_A_HEADER + p for p in p_list[1:-1]] +
                       [C_E_HEADER + p_list[-1]])

    if settings.session_trickle:
        for p in packet_list:
            c_queue.put(p)
    else:
        for p in packet_list:
            c_queue.put((p, settings))
示例#4
0
    def new_master_key(self) -> None:
        """Create a new master key from salt and password."""
        password = MasterKey.new_password()
        salt = csprng()
        rounds = ARGON2_ROUNDS
        memory = ARGON2_MIN_MEMORY

        phase("Deriving master key", head=2)
        while True:
            time_start = time.monotonic()
            master_key, parallellism = argon2_kdf(password,
                                                  salt,
                                                  rounds,
                                                  memory=memory,
                                                  local_test=self.local_test)
            time_final = time.monotonic() - time_start

            if time_final > 3.0:
                self.master_key = master_key
                ensure_dir(f'{DIR_USER_DATA}/')
                with open(self.file_name, 'wb+') as f:
                    f.write(salt + hash_chain(self.master_key) +
                            int_to_bytes(rounds) + int_to_bytes(memory) +
                            int_to_bytes(parallellism))
                phase(DONE)
                break
            else:
                memory *= 2
示例#5
0
    def new_master_key(self) -> None:
        """Create a new master key from salt and password.

        The number of rounds starts at 1 but is increased dynamically based
        on system performance. This allows more security on faster platforms
        without additional cost on key derivation time.
        """
        password = MasterKey.new_password()
        salt = keygen()
        rounds = 1

        phase("Deriving master key", head=2)
        while True:
            time_start = time.monotonic()
            master_key, memory = argon2_kdf(password,
                                            salt,
                                            rounds,
                                            local_testing=self.local_test)
            time_final = time.monotonic() - time_start

            if time_final > 3.0:
                self.master_key = master_key
                master_key_hash = hash_chain(master_key)
                ensure_dir(f'{DIR_USER_DATA}/')
                with open(self.file_name, 'wb+') as f:
                    f.write(salt + master_key_hash + int_to_bytes(rounds) +
                            int_to_bytes(memory))
                phase('Done')
                break
            else:
                rounds *= 2
示例#6
0
文件: encoding.py 项目: barleyj/tfc
def b58encode(byte_string: bytes) -> str:
    """Encode byte string to checksummed Base58 string.

    This format is very similar to Bitcoin's Wallet Import Format,
    however the SHA256(SHA256(key)) checksum has been replaced with
    TFC's hash chain (truncated to 4 bytes).
    """
    b58_alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

    byte_string += hash_chain(byte_string)[:4]

    orig_len = len(byte_string)
    byte_string = byte_string.lstrip(b'\x00')
    new_len = len(byte_string)

    p, acc = 1, 0
    for byte in bytearray(byte_string[::-1]):
        acc += p * byte
        p *= 256

    encoded = ''
    while acc > 0:
        acc, mod = divmod(acc, 58)
        encoded += b58_alphabet[mod]

    return (encoded + (orig_len - new_len) * '1')[::-1]
示例#7
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)
示例#8
0
文件: db_keys.py 项目: AJMartel/tfc
 def rotate_tx_key(self) -> None:
     """\
     Update TxM side tx-key and harac (provides
     forward secrecy for sent messages).
     """
     self.tx_key = hash_chain(self.tx_key)
     self.tx_harac += 1
     self.store_keys()
示例#9
0
    def test_decryption_with_zero_rx_hek_raises_fr(self):
        # Setup
        self.create_encrypted_packet(tx_harac=2, rx_harac=1, key=(hash_chain(KEY_LENGTH*b'\x01')))
        keyset        = self.key_list.get_keyset('*****@*****.**')
        keyset.rx_hek = bytes(KEY_LENGTH)

        # Test
        self.assertFR("Warning! Loaded zero-key for packet decryption.",
                      decrypt_assembly_packet, self.packet, self.window_list, self.contact_list, self.key_list)
示例#10
0
    def test_successful_packet_decryption_with_offset(self):
        # Setup
        self.create_encrypted_packet(tx_harac=2, rx_harac=1, key=(hash_chain(KEY_LENGTH*b'\x01')))

        # Test
        assembly_pt, account, origin = decrypt_assembly_packet(self.packet, self.window_list, self.contact_list, self.key_list)
        self.assertEqual(rm_padding_bytes(assembly_pt), PRIVATE_MESSAGE_HEADER + b'test')
        self.assertEqual(account, '*****@*****.**')
        self.assertEqual(origin, ORIGIN_CONTACT_HEADER)
示例#11
0
def split_to_assembly_packets(payload: bytes, p_type: str) -> List[bytes]:
    """Split payload to assembly packets.

    Messages and commands are compressed to reduce transmission time.
    Files have been compressed at earlier phase, before B85 encoding.

    If the compressed message can not be sent over one packet, it is
    split into multiple assembly packets with headers. Long messages
    are encrypted with inner layer of XSalsa20-Poly1305 to provide
    sender based control over partially transmitted data. Regardless
    of packet size, files always have an inner layer of encryption,
    and it is added in earlier phase. Commands do not need
    sender-based control, so they are only delivered with hash that
    makes integrity check easy.

    First assembly packet in file transmission is prepended with 8-byte
    packet counter that tells sender and receiver how many packets the
    file transmission requires.
    """
    s_header = {MESSAGE: M_S_HEADER, FILE: F_S_HEADER, COMMAND: C_S_HEADER}[p_type]
    l_header = {MESSAGE: M_L_HEADER, FILE: F_L_HEADER, COMMAND: C_L_HEADER}[p_type]
    a_header = {MESSAGE: M_A_HEADER, FILE: F_A_HEADER, COMMAND: C_A_HEADER}[p_type]
    e_header = {MESSAGE: M_E_HEADER, FILE: F_E_HEADER, COMMAND: C_E_HEADER}[p_type]

    if p_type in [MESSAGE, COMMAND]:
        payload = zlib.compress(payload, level=COMPRESSION_LEVEL)

    if len(payload) < PADDING_LEN:
        padded      = byte_padding(payload)
        packet_list = [s_header + padded]

    else:
        if p_type == MESSAGE:
            msg_key = csprng()
            payload = encrypt_and_sign(payload, msg_key)
            payload += msg_key

        elif p_type == FILE:
            payload = bytes(FILE_PACKET_CTR_LEN) + payload

        elif p_type == COMMAND:
            payload += hash_chain(payload)

        padded = byte_padding(payload)
        p_list = split_byte_string(padded, item_len=PADDING_LEN)

        if p_type == FILE:
            p_list[0] = int_to_bytes(len(p_list)) + p_list[0][FILE_PACKET_CTR_LEN:]

        packet_list = ([l_header + p_list[0]] +
                       [a_header + p for p in p_list[1:-1]] +
                       [e_header + p_list[-1]])

    return packet_list
示例#12
0
 def mock_command_preprocessor(command):
     payload = zlib.compress(command, level=9)
     if len(payload) < 255:
         padded      = byte_padding(payload)
         packet_list = [C_S_HEADER + padded]
     else:
         payload += hash_chain(payload)
         padded   = byte_padding(payload)
         p_list   = split_byte_string(padded, item_len=255)
         packet_list = ([C_L_HEADER + p_list[0]] +
                        [C_A_HEADER + p for p in p_list[1:-1]] +
                        [C_E_HEADER + p_list[-1]])
     return packet_list
示例#13
0
def assembly_packet_creator(p_type: str,
                            payload: bytes = b'',
                            origin: bytes = b'',
                            header: bytes = b'',
                            group_name: str = None,
                            encrypt: bool = False,
                            break_g_name: bool = False,
                            origin_acco: bytes = b'*****@*****.**'):
    """Create assembly packet list and optionally encrypt it."""
    if p_type == MESSAGE:
        if not header:
            if group_name is not None:
                group_msg_id = GROUP_MSG_ID_LEN * b'a'
                group_name = binascii.unhexlify(
                    'a466c02c221cb135') if break_g_name else group_name.encode(
                    )
                header = GROUP_MESSAGE_HEADER + group_msg_id + group_name + US_BYTE
            else:
                header = PRIVATE_MESSAGE_HEADER
        payload = header + payload

    if p_type == FILE:
        if not payload:
            compressed = zlib.compress(os.urandom(10000),
                                       level=COMPRESSION_LEVEL)
            file_key = os.urandom(KEY_LENGTH)
            encrypted = encrypt_and_sign(compressed, key=file_key) + file_key
            encoded = base64.b85encode(encrypted)
            payload = int_to_bytes(1) + int_to_bytes(
                2) + b'testfile.txt' + US_BYTE + encoded

    packet_list = split_to_assembly_packets(payload, p_type)

    if not encrypt:
        return packet_list

    if encrypt:
        harac = 1
        m_key = KEY_LENGTH * b'\x01'
        m_hek = KEY_LENGTH * b'\x01'
        assembly_ct_list = []
        for p in packet_list:
            harac_in_bytes = int_to_bytes(harac)
            encrypted_harac = encrypt_and_sign(harac_in_bytes, m_hek)
            encrypted_message = encrypt_and_sign(p, m_key)
            encrypted_packet = MESSAGE_PACKET_HEADER + encrypted_harac + encrypted_message + origin + origin_acco
            assembly_ct_list.append(encrypted_packet)
            m_key = hash_chain(m_key)
            harac += 1

        return assembly_ct_list
示例#14
0
    def assemble_command_packet(self) -> bytes:
        """Assemble command packet."""
        padded = b''.join([p[1:] for p in self.assembly_pt_list])
        payload = rm_padding_bytes(padded)

        if len(self.assembly_pt_list) > 1:
            cmd_hash = payload[-KEY_LENGTH:]
            payload = payload[:-KEY_LENGTH]
            if hash_chain(payload) != cmd_hash:
                raise FunctionReturn("Error: Received an invalid command.")

        try:
            return zlib.decompress(payload)
        except zlib.error:
            raise FunctionReturn("Error: Decompression of command failed.")
示例#15
0
        def queue_delayer():
            time.sleep(0.1)

            # Queue local key packet
            local_key_packet = LOCAL_KEY_PACKET_HEADER + encrypt_and_sign(
                local_key + local_hek + conf_code, key=kek)
            queues[LOCAL_KEY_PACKET_HEADER].put(
                (datetime.datetime.now(), local_key_packet))
            time.sleep(0.1)

            # Queue screen clearing command
            queue_packet(tx_key, tx_hek, INITIAL_HARAC, CLEAR_SCREEN_HEADER)

            # Queue message that goes to buffer
            queue_packet(tx_key, tx_hek, INITIAL_HARAC,
                         PRIVATE_MESSAGE_HEADER + b'Hi Bob', b'*****@*****.**')

            # Queue public key for Bob
            public_key_packet = PUBLIC_KEY_PACKET_HEADER + KEY_LENGTH * b'a' + ORIGIN_CONTACT_HEADER + b'*****@*****.**'
            queues[PUBLIC_KEY_PACKET_HEADER].put(
                (datetime.datetime.now(), public_key_packet))
            time.sleep(0.1)

            # Queue X25519 keyset for Bob
            command = KEY_EX_X25519_HEADER + 4 * (
                KEY_LENGTH * b'a') + b'*****@*****.**' + US_BYTE + b'Bob'
            queue_packet(hash_chain(tx_key), tx_hek, INITIAL_HARAC + 1,
                         command)

            # Queue window selection packet
            command = WINDOW_SELECT_HEADER + b'*****@*****.**'
            queue_packet(hash_chain(hash_chain(tx_key)), tx_hek,
                         INITIAL_HARAC + 2, command)

            # Queue message that is displayed directly
            packet = b'Hi again, Bob'
            queue_packet(tx_key, tx_hek, INITIAL_HARAC, packet,
                         b'*****@*****.**')

            # Queue file window selection command
            command = WINDOW_SELECT_HEADER + WIN_TYPE_FILE.encode()
            queue_packet(hash_chain(hash_chain(hash_chain(tx_key))), tx_hek,
                         INITIAL_HARAC + 3, command)

            # Queue imported file packet
            file_data = str_to_bytes('testfile') + 500 * b'a'
            compressed = zlib.compress(file_data, level=COMPRESSION_LEVEL)
            packet = IMPORTED_FILE_HEADER + encrypt_and_sign(compressed,
                                                             key=fdk)
            queues[IMPORTED_FILE_HEADER].put((datetime.datetime.now(), packet))
            time.sleep(0.1)

            # Queue exit message to break loop
            queues[UNITTEST_QUEUE].put(EXIT)
            time.sleep(0.1)
示例#16
0
    def test_long_command_compression_error_raises_fr(self):
        # Setup
        packet      = Packet(LOCAL_ID, self.contact, ORIGIN_CONTACT_HEADER, COMMAND, self.settings)
        command     = os.urandom(500) + b'a'
        payload     = zlib.compress(command, level=COMPRESSION_LEVEL)[::-1]
        payload    += hash_chain(payload)
        padded      = byte_padding(payload)
        p_list      = split_byte_string(padded, item_len=PADDING_LEN)
        packet_list = ([C_L_HEADER + p_list[0]] +
                       [C_A_HEADER + p for p in p_list[1:-1]] +
                       [C_E_HEADER + p_list[-1]])

        for p in packet_list:
            packet.add_packet(p)

        # Test
        self.assertFR("Error: Decompression of command failed.", packet.assemble_command_packet)
        self.assertEqual(packet.log_masking_ctr, 0)
示例#17
0
    def test_successful_packet_decryption_with_offset(self):
        # Setup
        message           = PRIVATE_MESSAGE_HEADER + byte_padding(b'test')
        encrypted_message = encrypt_and_sign(message, hash_chain(32 * b'\x01'))
        harac_in_bytes    = int_to_bytes(2)
        encrypted_harac   = encrypt_and_sign(harac_in_bytes, 32 * b'\x01')
        packet            = MESSAGE_PACKET_HEADER + encrypted_harac + encrypted_message + ORIGIN_CONTACT_HEADER + b'*****@*****.**'

        window_list       = WindowList(nicks=['Alice', 'local'])
        contact_list      = ContactList(nicks=['Alice', 'local'])
        key_list          = KeyList(nicks=['Alice', 'local'])
        keyset            = key_list.get_keyset('*****@*****.**')
        keyset.rx_harac   = 1

        # Test
        assembly_pt, account, origin = decrypt_assembly_packet(packet, window_list, contact_list, key_list)

        self.assertEqual(assembly_pt, message)
        self.assertEqual(account, '*****@*****.**')
        self.assertEqual(origin, ORIGIN_CONTACT_HEADER)
示例#18
0
    def new_master_key(self):
        password = MasterKey.new_password()
        salt     = os.urandom(32)
        rounds   = 1

        assert isinstance(salt, bytes)
        while True:
            time_start         = time.monotonic()
            master_key, memory = argon2_kdf(password, salt, rounds, local_testing=False)
            time_final         = time.monotonic() - time_start

            if time_final > 3.0:
                self.master_key = master_key
                master_key_hash = hash_chain(master_key)
                ensure_dir(f'{DIR_USER_DATA}/')
                with open(self.file_name, 'wb+') as f:
                    f.write(salt
                            + master_key_hash
                            + int_to_bytes(rounds)
                            + int_to_bytes(memory))
                break
            else:
                rounds *= 2
示例#19
0
    def create_file_apct():
        def mock_file_preprocessor(payload):
            payload = bytes(8) + payload
            padded = byte_padding(payload)
            p_list = split_byte_string(padded, item_len=255)

            packet_list = (
                [F_L_HEADER + int_to_bytes(len(p_list)) + p_list[0][8:]] +
                [F_A_HEADER + p
                 for p in p_list[1:-1]] + [F_E_HEADER + p_list[-1]])
            return packet_list

        file_data = os.urandom(10000)
        compressed = zlib.compress(file_data, level=9)
        file_key = os.urandom(32)
        encrypted = encrypt_and_sign(compressed, key=file_key)
        encrypted += file_key
        encoded = base64.b85encode(encrypted)

        file_data = US_BYTE.join(
            [b'testfile.txt', b'11.0B', b'00d 00h 00m 00s', encoded])
        packets = mock_file_preprocessor(file_data)

        harac = 1
        m_key = 32 * b'\x01'
        apctl = []

        for p in packets:
            harac_in_bytes = int_to_bytes(harac)
            encrypted_harac = encrypt_and_sign(harac_in_bytes, 32 * b'\x01')
            encrypted_message = encrypt_and_sign(p, m_key)
            encrypted_packet = MESSAGE_PACKET_HEADER + encrypted_harac + encrypted_message + ORIGIN_CONTACT_HEADER + b'*****@*****.**'
            apctl.append(encrypted_packet)
            harac += 1
            m_key = hash_chain(m_key)

        return apctl
示例#20
0
    def load_master_key(self) -> None:
        """Derive master key from password and salt."""
        with open(self.file_name, 'rb') as f:
            data = f.read()
        salt = data[0:32]
        key_hash = data[32:64]
        rounds = bytes_to_int(data[64:72])
        memory = bytes_to_int(data[72:80])
        parallelism = bytes_to_int(data[80:88])

        while True:
            password = MasterKey.get_password()
            phase("Deriving master key", head=2, offset=16)
            purp_key, _ = argon2_kdf(password, salt, rounds, memory,
                                     parallelism)

            if hash_chain(purp_key) == key_hash:
                self.master_key = purp_key
                phase("Password correct", done=True)
                clear_screen(delay=0.5)
                break
            else:
                phase("Invalid password", done=True)
                print_on_previous_line(reps=5, delay=1)
示例#21
0
    def create_message_apct(origin, message, header=None, group_name=None):
        if not header:
            if group_name is not None:
                timestamp = double_to_bytes(time.time() * 1000)
                header = GROUP_MESSAGE_HEADER + timestamp + group_name + US_BYTE
            else:
                header = PRIVATE_MESSAGE_HEADER

        plaintext = header + message
        payload = zlib.compress(plaintext, level=9)
        if len(payload) < 255:
            padded = byte_padding(payload)
            packet_list = [M_S_HEADER + padded]
        else:
            msg_key = os.urandom(32)
            payload = encrypt_and_sign(payload, msg_key)
            payload += msg_key
            padded = byte_padding(payload)
            p_list = split_byte_string(padded, item_len=255)

            packet_list = ([M_L_HEADER + p_list[0]] +
                           [M_A_HEADER + p
                            for p in p_list[1:-1]] + [M_E_HEADER + p_list[-1]])

        harac = 1
        m_key = 32 * b'\x01'
        apctl = []
        for p in packet_list:
            harac_in_bytes = int_to_bytes(harac)
            encrypted_harac = encrypt_and_sign(harac_in_bytes, 32 * b'\x01')
            encrypted_message = encrypt_and_sign(p, m_key)
            encrypted_packet = MESSAGE_PACKET_HEADER + encrypted_harac + encrypted_message + origin + b'*****@*****.**'
            apctl.append(encrypted_packet)
            harac += 1
            m_key = hash_chain(m_key)
        return apctl
示例#22
0
 def test_chain(self):
     """Sanity check after verifying function. No official test vectors exist."""
     self.assertEqual(
         hash_chain(bytes(32)),
         binascii.unhexlify('8d8c36497eb93a6355112e253f705a32'
                            '85f3e2d82b9ac29461cd8d4f764e5d41'))
示例#23
0
 def test_rotate_tx_key(self):
     self.assertIsNone(self.keyset.rotate_tx_key())
     self.assertEqual(self.keyset.tx_key, hash_chain(KEY_LENGTH * b'\x00'))
     self.assertEqual(self.keyset.tx_harac, 1)
示例#24
0
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)
示例#25
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)
示例#26
0
 def test_chain(self):
     """Sanity check after verifying function. No official vectors are available."""
     self.assertEqual(
         hash_chain(bytes(32)),
         binascii.unhexlify("8d8c36497eb93a6355112e253f705a32"
                            "85f3e2d82b9ac29461cd8d4f764e5d41"))