Exemple #1
0
    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]
Exemple #2
0
def group_management_print(
        key: str,  # Group management message identifier
        members: List[bytes],  # List of members' Onion public keys
        contact_list: 'ContactList',  # ContactList object
        group_name: str = ''  # Name of the group
) -> None:
    """Print group management command results."""
    m = {
        NEW_GROUP:
        "Created new group '{}' with following members:".format(group_name),
        ADDED_MEMBERS:
        "Added following accounts to group '{}':".format(group_name),
        ALREADY_MEMBER:
        "Following accounts were already in group '{}':".format(group_name),
        REMOVED_MEMBERS:
        "Removed following members from group '{}':".format(group_name),
        NOT_IN_GROUP:
        "Following accounts were not in group '{}':".format(group_name),
        UNKNOWN_ACCOUNTS:
        "Following unknown accounts were ignored:"
    }[key]

    if members:
        m_list = ([
            contact_list.get_nick_by_pub_key(m)
            for m in members if contact_list.has_pub_key(m)
        ] + [
            pub_key_to_onion_address(m)
            for m in members if not contact_list.has_pub_key(m)
        ])

        just_len = max(len(m) for m in m_list)
        justified = [m] + [f"  * {m.ljust(just_len)}" for m in m_list]
        m_print(justified, box=True)
Exemple #3
0
def onion_service(queues: Dict[bytes, 'Queue[Any]']) -> None:
    """Manage the Tor Onion Service and control Tor via stem."""
    rp_print("Setup   0% - Waiting for Onion Service configuration...",
             bold=True)
    while queues[ONION_KEY_QUEUE].qsize() == 0:
        time.sleep(0.1)

    private_key, c_code = queues[ONION_KEY_QUEUE].get()  # type: bytes, bytes
    public_key_user = bytes(
        nacl.signing.SigningKey(seed=private_key).verify_key)
    onion_addr_user = pub_key_to_onion_address(public_key_user)
    buffer_key = hashlib.blake2b(BUFFER_KEY,
                                 key=private_key,
                                 digest_size=SYMMETRIC_KEY_LENGTH).digest()

    try:
        rp_print("Setup  10% - Launching Tor...", bold=True)
        tor_port = get_available_port(1000, 65535)
        tor = Tor()
        tor.connect(tor_port)
    except (EOFError, KeyboardInterrupt):
        return

    if tor.controller is None:
        raise CriticalError("No Tor controller")

    try:
        rp_print("Setup  75% - Launching Onion Service...", bold=True)
        key_data = stem_compatible_ed25519_key_from_private_key(private_key)
        response = tor.controller.create_ephemeral_hidden_service(
            ports={80: 5000},
            key_type='ED25519-V3',
            key_content=key_data,
            await_publication=True)
        rp_print("Setup 100% - Onion Service is now published.", bold=True)

        m_print([
            "Your TFC account is:", onion_addr_user, '',
            f"Onion Service confirmation code (to Transmitter): {c_code.hex()}"
        ],
                box=True)

        # Allow the client to start looking for contacts at this point.
        queues[TOR_DATA_QUEUE].put((tor_port, onion_addr_user))
        queues[USER_ACCOUNT_QUEUE].put(onion_addr_user)

        # Pass buffer key to related processes
        queues[TX_BUF_KEY_QUEUE].put(buffer_key)
        queues[RX_BUF_KEY_QUEUE].put(buffer_key)

    except (KeyboardInterrupt, stem.SocketClosed):
        tor.stop()
        return

    monitor_queues(tor, response, queues)
Exemple #4
0
    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)
Exemple #5
0
    def __init__(self, uid: bytes, contact_list: 'ContactList',
                 group_list: 'GroupList', settings: 'Settings',
                 packet_list: 'PacketList') -> None:
        """Create a new RxWindow object."""
        self.uid = uid
        self.contact_list = contact_list
        self.group_list = group_list
        self.settings = settings
        self.packet_list = packet_list

        self.is_active = False
        self.contact = None
        self.group = None
        self.group_msg_id = os.urandom(GROUP_MSG_ID_LENGTH)

        self.window_contacts = []  # type: List[Contact]
        self.message_log = []  # type: List[MsgTuple]
        self.handle_dict = dict()  # type: Dict[bytes, str]
        self.previous_msg_ts = datetime.now()
        self.unread_messages = 0

        if self.uid == WIN_UID_COMMAND:
            self.type = WIN_TYPE_COMMAND  # type: str
            self.name = self.type  # type: str
            self.window_contacts = []

        elif self.uid == WIN_UID_FILE:
            self.type = WIN_TYPE_FILE
            self.packet_list = packet_list

        elif self.uid in self.contact_list.get_list_of_pub_keys():
            self.type = WIN_TYPE_CONTACT
            self.contact = self.contact_list.get_contact_by_pub_key(uid)
            self.name = self.contact.nick
            self.window_contacts = [self.contact]

        elif self.uid in self.group_list.get_list_of_group_ids():
            self.type = WIN_TYPE_GROUP
            self.group = self.group_list.get_group_by_id(self.uid)
            self.name = self.group.name
            self.window_contacts = self.group.members

        else:
            if len(uid) == ONION_SERVICE_PUBLIC_KEY_LENGTH:
                hr_uid = pub_key_to_onion_address(uid)
            elif len(uid) == GROUP_ID_LENGTH:
                hr_uid = b58encode(uid)
            else:
                hr_uid = "<unable to encode>"

            raise SoftError(f"Invalid window '{hr_uid}'.")
Exemple #6
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
Exemple #7
0
    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)
Exemple #8
0
 def test_conversion_back_and_forth(self):
     pub_key = os.urandom(SYMMETRIC_KEY_LENGTH)
     self.assertEqual(
         onion_address_to_pub_key(pub_key_to_onion_address(pub_key)),
         pub_key)
Exemple #9
0
def onion_service(queues: Dict[bytes, 'Queue[Any]']) -> None:
    """Manage the Tor Onion Service and control Tor via stem."""
    rp_print("Setup   0% - Waiting for Onion Service configuration...", bold=True)
    while queues[ONION_KEY_QUEUE].qsize() == 0:
        time.sleep(0.1)

    private_key, c_code = queues[ONION_KEY_QUEUE].get()  # type: bytes, bytes
    public_key_user     = bytes(nacl.signing.SigningKey(seed=private_key).verify_key)
    onion_addr_user     = pub_key_to_onion_address(public_key_user)

    try:
        rp_print("Setup  10% - Launching Tor...", bold=True)
        tor_port = get_available_port(1000, 65535)
        tor      = Tor()
        tor.connect(tor_port)
    except (EOFError, KeyboardInterrupt):
        return

    if tor.controller is None:
        raise CriticalError("No Tor controller")

    try:
        rp_print("Setup  75% - Launching Onion Service...", bold=True)
        key_data = stem_compatible_ed25519_key_from_private_key(private_key)
        response = tor.controller.create_ephemeral_hidden_service(ports={80: 5000},
                                                                  key_type='ED25519-V3',
                                                                  key_content=key_data,
                                                                  await_publication=True)
        rp_print("Setup 100% - Onion Service is now published.", bold=True)

        m_print(["Your TFC account is:",
                 onion_addr_user, '',
                 f"Onion Service confirmation code (to Transmitter): {c_code.hex()}"], box=True)

        # Allow the client to start looking for contacts at this point.
        queues[TOR_DATA_QUEUE].put((tor_port, onion_addr_user))

    except (KeyboardInterrupt, stem.SocketClosed):
        tor.stop()
        return

    while True:
        try:
            time.sleep(0.1)

            if queues[ONION_KEY_QUEUE].qsize() > 0:
                _, c_code = queues[ONION_KEY_QUEUE].get()

                m_print(["Onion Service is already running.", '',
                         f"Onion Service confirmation code (to Transmitter): {c_code.hex()}"], box=True)

            if queues[ONION_CLOSE_QUEUE].qsize() > 0:
                command = queues[ONION_CLOSE_QUEUE].get()
                if not tor.platform_is_tails() and command == EXIT:
                    tor.controller.remove_hidden_service(response.service_id)
                    tor.stop()
                queues[EXIT_QUEUE].put(command)
                time.sleep(5)
                break

        except (EOFError, KeyboardInterrupt):
            pass
        except stem.SocketClosed:
            tor.controller.remove_hidden_service(response.service_id)
            tor.stop()
            break
Exemple #10
0
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
Exemple #11
0
def nick_to_onion_address(nick: str) -> str:
    """Produce deterministic v3 Onion Service address from nick."""
    return pub_key_to_onion_address(nick_to_pub_key(nick))