def deliver_local_key(local_key_packet: bytes, kek: bytes, c_code: bytes, settings: 'Settings', queues: 'QueueDict') -> None: """Deliver encrypted local key to Destination Computer.""" nc_bypass_msg(NC_BYPASS_START, settings) queue_to_nc(local_key_packet, queues[RELAY_PACKET_QUEUE]) while True: print_key("Local key decryption key (to Receiver)", kek, settings) purp_code = ask_confirmation_code("Receiver") if purp_code == c_code.hex(): nc_bypass_msg(NC_BYPASS_STOP, settings) break elif purp_code == "": phase("Resending local key", head=2) queue_to_nc(local_key_packet, queues[RELAY_PACKET_QUEUE]) phase(DONE) print_on_previous_line( reps=(9 if settings.local_testing_mode else 10)) else: m_print([ "Incorrect confirmation code. If Receiver did not receive", "the encrypted local key, resend it by pressing <Enter>." ], head=1) print_on_previous_line( reps=(9 if settings.local_testing_mode else 10), delay=2)
def deliver_contact_data( header: bytes, # Key type (x448, PSK) nick: str, # Contact's nickname onion_pub_key: bytes, # Public key of contact's v3 Onion Service tx_mk: bytes, # Message key for outgoing messages rx_mk: bytes, # Message key for incoming messages tx_hk: bytes, # Header key for outgoing messages rx_hk: bytes, # Header key for incoming messages queues: 'QueueDict', # Dictionary of multiprocessing queues settings: 'Settings', # Settings object ) -> None: """Deliver contact data to Destination Computer.""" c_code = blake2b(onion_pub_key, digest_size=CONFIRM_CODE_LENGTH) command = (header + 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)
def rxp_load_psk(window: 'TxWindow', contact_list: 'ContactList', settings: 'Settings', queues: 'QueueDict', ) -> None: """Send command to Receiver Program to load PSK for active contact.""" if settings.traffic_masking: raise SoftError("Error: Command is disabled during traffic masking.", head_clear=True) if window.type == WIN_TYPE_GROUP or window.contact is None: raise SoftError("Error: Group is selected.", head_clear=True) if not contact_list.get_contact_by_pub_key(window.uid).uses_psk(): raise SoftError(f"Error: The current key was exchanged with {ECDHE}.", head_clear=True) c_code = blake2b(window.uid, digest_size=CONFIRM_CODE_LENGTH) command = KEY_EX_PSK_RX + c_code + window.uid queue_command(command, settings, queues) while True: try: purp_code = ask_confirmation_code('Receiver') if purp_code == c_code.hex(): window.contact.kex_status = KEX_STATUS_HAS_RX_PSK contact_list.store_contacts() raise SoftError(f"Removed PSK reminder for {window.name}.", tail_clear=True, delay=1) m_print("Incorrect confirmation code.", head=1) print_on_previous_line(reps=4, delay=2) except (EOFError, KeyboardInterrupt): raise SoftError("PSK install verification aborted.", tail_clear=True, delay=1, head=2)
def deliver_onion_service_data(relay_command: bytes, onion_service: 'OnionService', gateway: 'Gateway') -> None: """Send Onion Service data to Replay Program on Networked Computer.""" gateway.write(relay_command) while True: purp_code = ask_confirmation_code('Relay') if purp_code == onion_service.conf_code.hex(): onion_service.is_delivered = True onion_service.new_confirmation_code() break if purp_code == '': phase("Resending Onion Service data", head=2) gateway.write(relay_command) phase(DONE) print_on_previous_line(reps=5) else: m_print([ "Incorrect confirmation code. If Relay Program did not", "receive Onion Service data, resend it by pressing <Enter>." ], head=1) print_on_previous_line(reps=5, delay=2)
def test_ask_confirmation_code(self, _): self.assertEqual(ask_confirmation_code('Receiver'), self.confirmation_code)
def export_onion_service_data(contact_list: 'ContactList', settings: 'Settings', onion_service: 'OnionService', gateway: 'Gateway') -> None: """\ Send the Tor Onion Service's private key and list of Onion Service public keys of contacts to Relay Program on Networked Computer. This private key is not intended to be used by the Transmitter Program. Because the Networked Computer we are exporting it to might not store data, we use the trusted Source Computer to generate the private key and store it safely. The private key is needed by Tor on Networked Computer to start the Onion Service. Exporting this private key does not endanger message confidentiality because TFC uses a separate key exchange with separate private key to create the symmetric keys that protect the messages. That private key is never exported to the Networked Computer. Access to this key does not give any to user any information other than the v3 Onion Address. However, if they have compromised Relay Program to gain access to the key, they can see its public part anyway. This key is used by Tor to sign Diffie-Hellman public keys used when clients of contacts establish a secure connection to the Onion Service. This key can't be used to decrypt traffic retrospectively. The worst possible case in the situation of key compromise is, the key allows the attacker to start their own copy of the user's Onion Service. This does not allow impersonating as the user however, because the attacker is not in possession of keys that allow them to create valid ciphertexts. Even if they inject TFC public keys to conduct a MITM attack, that attack will be detected during fingerprint comparison. In addition to the private key, the Onion Service data packet also transmits the list of Onion Service public keys of existing and pending contacts to the Relay Program, as well as the setting that determines whether contact requests are allowed. Bundling all this data in a single packet is great in the sense a single confirmation code can be used to ensure that Relay Program has all the information necessary to perform its duties. """ m_print("Onion Service setup", bold=True, head_clear=True, head=1, tail=1) pending_contacts = b''.join(contact_list.get_list_of_pending_pub_keys()) existing_contacts = b''.join(contact_list.get_list_of_existing_pub_keys()) no_pending = int_to_bytes(len(contact_list.get_list_of_pending_pub_keys())) contact_data = no_pending + pending_contacts + existing_contacts relay_command = (UNENCRYPTED_DATAGRAM_HEADER + UNENCRYPTED_ONION_SERVICE_DATA + onion_service.onion_private_key + onion_service.conf_code + bool_to_bytes(settings.allow_contact_requests) + contact_data) gateway.write(relay_command) while True: purp_code = ask_confirmation_code('Relay') if purp_code == onion_service.conf_code.hex(): onion_service.is_delivered = True onion_service.new_confirmation_code() break elif purp_code == '': phase("Resending Onion Service data", head=2) gateway.write(relay_command) phase(DONE) print_on_previous_line(reps=5) else: m_print([ "Incorrect confirmation code. If Relay Program did not", "receive Onion Service data, resend it by pressing <Enter>." ], head=1) print_on_previous_line(reps=5, delay=2)
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)
def new_local_key(contact_list: 'ContactList', settings: 'Settings', queues: 'QueueDict') -> None: """Run local key exchange protocol. Local key encrypts commands and data sent from Source Computer to user's Destination Computer. The key is delivered to Destination Computer in packet encrypted with an ephemeral, symmetric, key encryption key. The check-summed Base58 format key decryption key is typed to Receiver Program manually. This prevents local key leak in following scenarios: 1. CT is intercepted by an adversary on compromised Networked Computer, but no visual eavesdropping takes place. 2. CT is not intercepted by an adversary on Networked Computer, but visual eavesdropping records key decryption key. 3. CT is delivered from Source Computer to Destination Computer directly (bypassing compromised Networked Computer), and visual eavesdropping records key decryption key. Once the correct key decryption key is entered to Receiver Program, it will display the 2-hexadecimal confirmation code generated by the Transmitter Program. The code will be entered back to Transmitter Program to confirm the user has successfully delivered the key decryption key. The protocol is completed with Transmitter Program sending LOCAL_KEY_RDY signal to the Receiver Program, that then moves to wait for public keys from contact. """ try: if settings.traffic_masking and contact_list.has_local_contact(): raise FunctionReturn( "Error: Command is disabled during traffic masking.", head_clear=True) m_print("Local key setup", bold=True, head_clear=True, head=1, tail=1) if not contact_list.has_local_contact(): time.sleep(0.5) key = csprng() hek = csprng() kek = csprng() c_code = os.urandom(CONFIRM_CODE_LENGTH) local_key_packet = LOCAL_KEY_DATAGRAM_HEADER + encrypt_and_sign( plaintext=key + hek + c_code, key=kek) # Deliver local key to Destination computer nc_bypass_msg(NC_BYPASS_START, settings) queue_to_nc(local_key_packet, queues[RELAY_PACKET_QUEUE]) while True: print_key("Local key decryption key (to Receiver)", kek, settings) purp_code = ask_confirmation_code('Receiver') if purp_code == c_code.hex(): nc_bypass_msg(NC_BYPASS_STOP, settings) break elif purp_code == '': phase("Resending local key", head=2) queue_to_nc(local_key_packet, queues[RELAY_PACKET_QUEUE]) phase(DONE) print_on_previous_line( reps=(9 if settings.local_testing_mode else 10)) else: m_print([ "Incorrect confirmation code. If Receiver did not receive", "the encrypted local key, resend it by pressing <Enter>." ], head=1) print_on_previous_line( reps=(9 if settings.local_testing_mode else 10), delay=2) # Add local contact to contact list database contact_list.add_contact(LOCAL_PUBKEY, LOCAL_NICK, bytes(FINGERPRINT_LENGTH), bytes(FINGERPRINT_LENGTH), KEX_STATUS_LOCAL_KEY, False, False, False) # Add local contact to keyset database queues[KEY_MANAGEMENT_QUEUE].put( (KDB_ADD_ENTRY_HEADER, LOCAL_PUBKEY, key, csprng(), hek, csprng())) # Notify Receiver that confirmation code was successfully entered queue_command(LOCAL_KEY_RDY, settings, queues) m_print("Successfully completed the local key exchange.", bold=True, tail_clear=True, delay=1, head=1) os.system(RESET) except (EOFError, KeyboardInterrupt): raise FunctionReturn("Local key setup aborted.", tail_clear=True, delay=1, head=2)
def create_pre_shared_key( onion_pub_key: bytes, # Public key of contact's v3 Onion Service nick: str, # Nick of contact contact_list: 'ContactList', # Contact list object settings: 'Settings', # Settings object onion_service: 'OnionService', # OnionService object queues: 'QueueDict' # Dictionary of multiprocessing queues ) -> None: """Generate a new pre-shared key for manual key delivery. Pre-shared keys offer a low-tech solution against the slowly emerging threat of quantum computers. PSKs are less convenient and not usable in every scenario, but until a quantum-safe key exchange algorithm with reasonably short keys is standardized, TFC can't provide a better alternative against quantum computers. The generated keys are protected by a key encryption key, derived from a 256-bit salt and a password (that is to be shared with the recipient) using Argon2id key derivation function. The encrypted message and header keys are stored together with salt on a removable media. This media must be a never-before-used device from sealed packaging. Re-using an old device might infect Source Computer, and the malware could either copy sensitive data on that removable media, or Source Computer might start transmitting the sensitive data covertly over the serial interface to malware on Networked Computer. Once the key has been exported to the clean drive, contact data and keys are exported to the Receiver Program on Destination computer. The transmission is encrypted with the local key. """ try: tx_mk = csprng() tx_hk = csprng() salt = csprng() password = MasterKey.new_password("password for PSK") phase("Deriving key encryption key", head=2) kek = argon2_kdf(password, salt, ARGON2_PSK_TIME_COST, ARGON2_PSK_MEMORY_COST, ARGON2_PSK_PARALLELISM) phase(DONE) ct_tag = encrypt_and_sign(tx_mk + tx_hk, key=kek) while True: trunc_addr = pub_key_to_short_address(onion_pub_key) store_d = ask_path_gui(f"Select removable media for {nick}", settings) f_name = f"{store_d}/{onion_service.user_short_address}.psk - Give to {trunc_addr}" try: with open(f_name, 'wb+') as f: f.write(salt + ct_tag) break except PermissionError: m_print( "Error: Did not have permission to write to the directory.", delay=0.5) continue c_code = blake2b(onion_pub_key, digest_size=CONFIRM_CODE_LENGTH) command = (KEY_EX_PSK_TX + onion_pub_key + tx_mk + csprng() + tx_hk + csprng() + 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) contact_list.add_contact(onion_pub_key, nick, bytes(FINGERPRINT_LENGTH), bytes(FINGERPRINT_LENGTH), KEX_STATUS_NO_RX_PSK, settings.log_messages_by_default, settings.accept_files_by_default, settings.show_notifications_by_default) queues[KEY_MANAGEMENT_QUEUE].put( (KDB_ADD_ENTRY_HEADER, onion_pub_key, tx_mk, csprng(), tx_hk, csprng())) m_print(f"Successfully added {nick}.", bold=True, tail_clear=True, delay=1, head=1) except (EOFError, KeyboardInterrupt): raise FunctionReturn("PSK generation aborted.", tail_clear=True, delay=1, head=2)
def new_local_key(contact_list: 'ContactList', settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None: """Run Tx-side local key exchange protocol. Local key encrypts commands and data sent from TxM to RxM. The key is delivered to RxM in packet encrypted with an ephemeral symmetric key. The checksummed Base58 format key decryption key is typed on RxM manually. This prevents local key leak in following scenarios: 1. CT is intercepted by adversary on compromised NH but no visual eavesdropping takes place. 2. CT is not intercepted by adversary on NH but visual eavesdropping records decryption key. 3. CT is delivered from TxM to RxM (compromised NH is bypassed) and visual eavesdropping records decryption key. Once correct key decryption key is entered on RxM, Receiver program will display the 1-byte confirmation code generated by Transmitter program. The code will be entered on TxM to confirm user has successfully delivered the key decryption key. The protocol is completed with Transmitter program sending an ACK message to Receiver program, that then moves to wait for public keys from contact. """ try: if settings.session_traffic_masking and contact_list.has_local_contact: raise FunctionReturn("Error: Command is disabled during traffic masking.") clear_screen() c_print("Local key setup", head=1, tail=1) c_code = os.urandom(1) key = csprng() hek = csprng() kek = csprng() packet = LOCAL_KEY_PACKET_HEADER + encrypt_and_sign(key + hek + c_code, key=kek) nh_bypass_msg(NH_BYPASS_START, settings) queue_to_nh(packet, settings, queues[NH_PACKET_QUEUE]) while True: print_key("Local key decryption key (to RxM)", kek, settings) purp_code = ask_confirmation_code() if purp_code == c_code.hex(): break elif purp_code == RESEND: phase("Resending local key", head=2) queue_to_nh(packet, settings, queues[NH_PACKET_QUEUE]) phase(DONE) print_on_previous_line(reps=(9 if settings.local_testing_mode else 10)) else: box_print(["Incorrect confirmation code. If RxM did not receive", "encrypted local key, resend it by typing 'resend'."], head=1) print_on_previous_line(reps=(11 if settings.local_testing_mode else 12), delay=2) nh_bypass_msg(NH_BYPASS_STOP, settings) # 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, False) # Add local contact to keyset database queues[KEY_MANAGEMENT_QUEUE].put((KDB_ADD_ENTRY_HEADER, LOCAL_ID, key, csprng(), hek, csprng())) # Notify RxM that confirmation code was successfully entered queue_command(LOCAL_KEY_INSTALLED_HEADER, settings, queues[COMMAND_PACKET_QUEUE]) box_print("Successfully added a new local key.") clear_screen(delay=1) except KeyboardInterrupt: raise FunctionReturn("Local key setup aborted.", delay=1, head=3, tail_clear=True)
def test_ask_confirmation_code(self): self.assertEqual(ask_confirmation_code(), 'ff')