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]
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)
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)
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)
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}'.")
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
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)
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)
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
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
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))