def process_received_packet(ts: 'datetime', ts_bytes: bytes, header: bytes, payload_bytes: bytes, onion_pub_key: bytes, short_addr: str, queues: 'QueueDict', gateway: 'Gateway') -> None: """Process received packet.""" if header == PUBLIC_KEY_DATAGRAM_HEADER: if len(payload_bytes) == TFC_PUBLIC_KEY_LENGTH: msg = f"Received public key from {short_addr} at {ts.strftime('%b %d - %H:%M:%S.%f')[:-4]}:" print_key(msg, payload_bytes, gateway.settings, public_key=True) queues[PUB_KEY_SEND_QUEUE].put((onion_pub_key, payload_bytes)) elif header == MESSAGE_DATAGRAM_HEADER: queues[DST_MESSAGE_QUEUE].put(header + ts_bytes + onion_pub_key + ORIGIN_CONTACT_HEADER + payload_bytes) rp_print(f"Message from contact {short_addr}", ts) elif header in [ GROUP_MSG_INVITE_HEADER, GROUP_MSG_JOIN_HEADER, GROUP_MSG_MEMBER_ADD_HEADER, GROUP_MSG_MEMBER_REM_HEADER, GROUP_MSG_EXIT_GROUP_HEADER ]: queues[GROUP_MSG_QUEUE].put((header, payload_bytes, short_addr)) else: rp_print(f"Received invalid packet from {short_addr}", ts, bold=True)
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 process_public_key(ts: 'datetime', packet: bytes, window_list: 'WindowList', settings: 'Settings', pubkey_buf: Dict[str, bytes]) -> None: """Display contact's public key and add it to buffer.""" pub_key = packet[1:33] origin = packet[33:34] try: account = packet[34:].decode() except UnicodeError: raise FunctionReturn( "Error! Account for received public key had invalid encoding.") if origin not in [ORIGIN_CONTACT_HEADER, ORIGIN_USER_HEADER]: raise FunctionReturn( "Error! Received public key had an invalid origin header.") if origin == ORIGIN_CONTACT_HEADER: pubkey_buf[account] = pub_key print_key(f"Received public key from {account}:", pub_key, settings) local_win = window_list.get_local_window() pub_key_b58 = ' '.join( split_string(b58encode(pub_key), item_len=(51 if settings.local_testing_mode else 3))) local_win.add_new( ts, f"Received public key from {account}: {pub_key_b58}") elif origin == ORIGIN_USER_HEADER and account in pubkey_buf: clear_screen() print_key(f"Public key for {account}:", pubkey_buf[account], settings)
def export_file(settings: 'Settings', nh_queue: 'Queue') -> None: """Encrypt and export file to NH. This is a faster method to send large files. It is used together with file import (/fi) command that uploads ciphertext to RxM for RxM-side decryption. Key is generated automatically so that bad passwords selected by users do not affect security of ciphertexts. """ if settings.session_traffic_masking: raise FunctionReturn( "Error: Command is disabled during traffic masking.") path = ask_path_gui("Select file to export...", settings, get_file=True) name = path.split('/')[-1] data = bytearray() data.extend(str_to_bytes(name)) if not os.path.isfile(path): raise FunctionReturn("Error: File not found.") if os.path.getsize(path) == 0: raise FunctionReturn("Error: Target file is empty.") phase("Reading data") with open(path, 'rb') as f: data.extend(f.read()) phase(DONE) phase("Compressing data") comp = bytes(zlib.compress(bytes(data), level=COMPRESSION_LEVEL)) phase(DONE) phase("Encrypting data") file_key = csprng() file_ct = encrypt_and_sign(comp, key=file_key) phase(DONE) phase("Exporting data") queue_to_nh(EXPORTED_FILE_HEADER + file_ct, settings, nh_queue) phase(DONE) print_key(f"Decryption key for file '{name}':", file_key, settings, no_split=True, file_key=True)
def get_data_loop(onion_addr: str, url_token: str, short_addr: str, onion_pub_key: bytes, queues: 'QueueDict', session: 'Session', gateway: 'Gateway') -> None: """Load TFC data from contact's Onion Service using valid URL token.""" while True: try: # See if a file is available try: file_data = session.get( f'http://{onion_addr}.onion/{url_token}/files', stream=True).content if file_data: ts = datetime.now() ts_bytes = int_to_bytes( int(ts.strftime('%Y%m%d%H%M%S%f')[:-4])) packet = FILE_DATAGRAM_HEADER + ts_bytes + onion_pub_key + ORIGIN_CONTACT_HEADER + file_data queues[DST_MESSAGE_QUEUE].put(packet) rp_print(f"File from contact {short_addr}", ts) except requests.exceptions.RequestException: pass # See if messages are available try: r = session.get( f'http://{onion_addr}.onion/{url_token}/messages', stream=True) except requests.exceptions.RequestException: return None for line in r.iter_lines( ): # Iterates over newline-separated datagrams if not line: continue try: header, payload = separate_header( line, DATAGRAM_HEADER_LENGTH) # type: bytes, bytes payload_bytes = base64.b85decode(payload) except (UnicodeError, ValueError): continue ts = datetime.now() ts_bytes = int_to_bytes(int( ts.strftime('%Y%m%d%H%M%S%f')[:-4])) if header == PUBLIC_KEY_DATAGRAM_HEADER: if len(payload_bytes) == TFC_PUBLIC_KEY_LENGTH: msg = f"Received public key from {short_addr} at {ts.strftime('%b %d - %H:%M:%S.%f')[:-4]}:" print_key(msg, payload_bytes, gateway.settings, public_key=True) elif header == MESSAGE_DATAGRAM_HEADER: queues[DST_MESSAGE_QUEUE].put(header + ts_bytes + onion_pub_key + ORIGIN_CONTACT_HEADER + payload_bytes) rp_print(f"Message from contact {short_addr}", ts) elif header in [ GROUP_MSG_INVITE_HEADER, GROUP_MSG_JOIN_HEADER, GROUP_MSG_MEMBER_ADD_HEADER, GROUP_MSG_MEMBER_REM_HEADER, GROUP_MSG_EXIT_GROUP_HEADER ]: queues[GROUP_MSG_QUEUE].put( (header, payload_bytes, short_addr)) else: rp_print(f"Received invalid packet from {short_addr}", ts, bold=True) except requests.exceptions.RequestException: break
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 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)