def test_disabled_gui_uses_cli(self): # Setup self.settings.disable_gui_dialog = True builtins.input = lambda _: '/bin/mv' # Test self.assertEqual(ask_path_gui('prompt_msg', self.settings, get_file=True), '/bin/mv')
def test_tcl_error_falls_back_to_cli(self): # Setup builtins.input = lambda _: '/bin/mv' filedialog.askopenfilename = lambda title: (_ for _ in ()).throw(_tkinter.TclError) # Test self.assertEqual(ask_path_gui('prompt_msg', self.settings, get_file=True), '/bin/mv')
def store_keys_on_removable_drive( ct_tag: bytes, # Encrypted PSK salt: bytes, # Salt for PSK decryption key derivation nick: str, # Contact's nickname onion_pub_key: bytes, # Public key of contact's v3 Onion Service onion_service: 'OnionService', # OnionService object settings: 'Settings', # Settings object ) -> None: """Store keys for contact on a removable media.""" trunc_addr = pub_key_to_short_address(onion_pub_key) while True: 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) f.flush() os.fsync(f.fileno()) break except PermissionError: m_print( "Error: Did not have permission to write to the directory.", delay=0.5) continue
def new_psk(account: str, user: str, nick: str, contact_list: 'ContactList', settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None: """Generate new pre-shared key for manual key delivery. :param account: The contact's account name (e.g. [email protected]) :param user: The user's account name (e.g. [email protected]) :param nick: Nick of contact :param contact_list: Contact list object :param settings: Settings object :param queues: Dictionary of multiprocessing queues :return: None """ try: tx_key = keygen() tx_hek = keygen() salt = keygen() password = MasterKey.new_password("password for PSK") phase("Deriving key encryption key", head=2) kek, _ = argon2_kdf(password, salt, rounds=16, memory=128000, parallelism=1) phase('Done') ct_tag = encrypt_and_sign(tx_key + tx_hek, key=kek) store_d = ask_path_gui(f"Select removable media for {nick}", settings) f_name = f"{store_d}/{user}.psk - Give to {account}" try: with open(f_name, 'wb+') as f: f.write(salt + ct_tag) except PermissionError: raise FunctionReturn( "Error: Did not have permission to write to directory.") packet = KEY_EX_PSK_TX_HEADER \ + tx_key \ + tx_hek \ + account.encode() + US_BYTE + nick.encode() queue_command(packet, settings, queues[COMMAND_PACKET_QUEUE]) contact_list.add_contact(account, user, nick, bytes(32), bytes(32), settings.log_msg_by_default, settings.store_file_default, settings.n_m_notify_privacy) queues[KEY_MANAGEMENT_QUEUE].put( ('ADD', account, tx_key, bytes(32), tx_hek, bytes(32))) box_print([f"Successfully added {nick}."], head=1) clear_screen(delay=1) except KeyboardInterrupt: raise FunctionReturn("PSK generation aborted.")
def queue_file(window: 'TxWindow', settings: 'Settings', queues: 'QueueDict') -> None: """Ask file path and load file data. In TFC there are two ways to send a file. For traffic masking, the file is loaded and sent inside normal messages using assembly packet headers dedicated for file transmission. This transmission is much slower, so the File object will determine metadata about the transmission's estimated transfer time, number of packets and the name and size of file. This information is inserted to the first assembly packet so that the recipient can observe the transmission progress from file transfer window. When traffic masking is disabled, file transmission is much faster as the file is only encrypted and transferred over serial once before the Relay Program multi-casts the ciphertext to each specified recipient. See the send_file docstring (below) for more details. """ path = ask_path_gui("Select file to send...", settings, get_file=True) if path.endswith( ('tx_contacts', 'tx_groups', 'tx_keys', 'tx_login_data', 'tx_settings', 'rx_contacts', 'rx_groups', 'rx_keys', 'rx_login_data', 'rx_settings', 'tx_serial_settings.json', 'nc_serial_settings.json', 'rx_serial_settings.json', 'tx_onion_db')): raise FunctionReturn("Error: Can't send TFC database.", head_clear=True) if not settings.traffic_masking: send_file(path, settings, queues, window) return file = File(path, window, settings) assembly_packets = split_to_assembly_packets(file.plaintext, FILE) if settings.confirm_sent_files: try: if not yes( f"Send {file.name.decode()} ({file.size_hr}) to {window.type_print} {window.name} " f"({len(assembly_packets)} packets, time: {file.time_hr})?" ): raise FunctionReturn("File selection aborted.", head_clear=True) except (EOFError, KeyboardInterrupt): raise FunctionReturn("File selection aborted.", head_clear=True) queue_assembly_packets(assembly_packets, FILE, settings, queues, window, log_as_ph=True)
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 queue_file(window: 'TxWindow', settings: 'Settings', f_queue: 'Queue', gateway: 'Gateway') -> None: """Ask file path and load file data.""" path = ask_path_gui("Select file to send...", settings, get_file=True) file = File(path, window, settings, gateway) packet_list = split_to_assembly_packets(file.plaintext, FILE) if settings.confirm_sent_files: try: if not yes(f"Send {file.name.decode()} ({file.size_print}) to {window.type_print} {window.name} " f"({len(packet_list)} packets, time: {file.time_print})?"): raise FunctionReturn("File selection aborted.") except KeyboardInterrupt: raise FunctionReturn("File selection aborted.", head=3) queue_packets(packet_list, FILE, settings, f_queue, window, log_as_ph=True)
def queue_file(window: 'Window', settings: 'Settings', f_queue: 'Queue', gateway: 'Gateway') -> None: """Ask file path and load file data.""" path = ask_path_gui("Select file to send...", settings, get_file=True) file = File(path, window, settings, gateway) name = file.name.decode() size = file.size.decode() payload = file.plaintext if len(payload) < 255: padded = byte_padding(payload) packet_list = [F_S_HEADER + padded] else: payload = bytes(8) + payload padded = byte_padding(payload) p_list = split_byte_string(padded, item_len=255) # < number of packets > 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]]) for p in packet_list: assert len(p) == 256 if settings.confirm_sent_files: if not yes( f"Send {name} ({size}) to {window.type} {window.name} " f"({len(packet_list)} packets, time: {file.time_s})?", tail=1): raise FunctionReturn("File selection aborted.") if settings.session_trickle: log_m_dictionary = dict((c.rx_account, c.log_messages) for c in window) for p in packet_list: f_queue.put((p, log_m_dictionary)) else: for c in window: for p in packet_list: f_queue.put((p, settings, c.rx_account, c.tx_account, c.log_messages, window.uid))
def export_file(settings: 'Settings', gateway: 'Gateway'): """Encrypt and export file to NH. This is a faster method of sending large files. It is used together with '/fi' import_file command that loads ciphertext to RxM for later decryption. Key is generated automatically so that bad passwords by users do not affect security of ciphertexts. As use of this command reveals use of TFC, it is disabled during trickle connection. """ if settings.session_trickle: raise FunctionReturn("Command disabled during trickle connection.") 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. No file was sent.") 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=9)) phase("Done") phase("Encrypting data") file_key = keygen() file_ct = encrypt_and_sign(comp, key=file_key) phase("Done") phase("Exporting data") transmit(EXPORTED_FILE_CT_HEADER + file_ct, settings, gateway) phase("Done") box_print([f"Decryption key for file {name}:", '', b58encode(file_key)], head=1, tail=1)
def test_get_path_gui(self, _): self.assertEqual(ask_path_gui('select path for file:', self.settings), self.path)
def test_tcl_error_falls_back_to_cli(self, *_): self.assertEqual( ask_path_gui('prompt_msg', self.settings, get_file=True), self.file_path)
def test_get_path_to_file_gui(self, *_): self.assertEqual( ask_path_gui('path to file:', self.settings, get_file=True), self.file_path)
def test_get_path_gui(self): # Setup filedialog.askdirectory = lambda title: 'test_path' # Test self.assertEqual(ask_path_gui('test message', self.settings, get_file=False), 'test_path')
def test_disabled_gui_uses_cli(self, *_): self.settings.disable_gui_dialog = True self.assertEqual( ask_path_gui('prompt_msg', self.settings, get_file=True), self.file_path)
def key_ex_psk_rx(packet: bytes, ts: 'datetime', window_list: 'WindowList', contact_list: 'ContactList', key_list: 'KeyList', settings: 'Settings') -> None: """Import Rx-PSK of contact.""" c_code, onion_pub_key = separate_header(packet, CONFIRM_CODE_LENGTH) short_addr = pub_key_to_short_address(onion_pub_key) if not contact_list.has_pub_key(onion_pub_key): raise SoftError(f"Error: Unknown account '{short_addr}'.", head_clear=True) contact = contact_list.get_contact_by_pub_key(onion_pub_key) psk_file = ask_path_gui(f"Select PSK for {contact.nick} ({short_addr})", settings, get_file=True) try: with open(psk_file, 'rb') as f: psk_data = f.read() except PermissionError: raise SoftError("Error: No read permission for the PSK file.") if len(psk_data) != PSK_FILE_SIZE: raise SoftError("Error: The PSK data in the file was invalid.", head_clear=True) salt, ct_tag = separate_header(psk_data, ARGON2_SALT_LENGTH) psk = decrypt_rx_psk(ct_tag, salt) rx_mk, rx_hk = separate_header(psk, SYMMETRIC_KEY_LENGTH) if any(k == bytes(SYMMETRIC_KEY_LENGTH) for k in [rx_mk, rx_hk]): raise SoftError("Error: Received invalid keys from contact.", head_clear=True) keyset = key_list.get_keyset(onion_pub_key) keyset.rx_mk = rx_mk keyset.rx_hk = rx_hk key_list.store_keys() contact.kex_status = KEX_STATUS_HAS_RX_PSK contact_list.store_contacts() # Pipes protects against shell injection. Source of command's parameter is # the program itself, and therefore trusted, but it's still good practice. subprocess.Popen(f"shred -n 3 -z -u {pipes.quote(psk_file)}", shell=True).wait() if os.path.isfile(psk_file): m_print( f"Warning! Overwriting of PSK ({psk_file}) failed. Press <Enter> to continue.", manual_proceed=True, box=True) message = f"Added Rx-side PSK for {contact.nick} ({short_addr})." cmd_win = window_list.get_command_window() cmd_win.add_new(ts, message) m_print([ message, '', "Warning!", "Physically destroy the keyfile transmission media ", "to ensure it does not steal data from this computer!", '', f"Confirmation code (to Transmitter): {c_code.hex()}" ], box=True, head=1, tail=1)
def key_ex_psk_rx(packet: bytes, ts: 'datetime', window_list: 'WindowList', contact_list: 'ContactList', key_list: 'KeyList', settings: 'Settings') -> None: """Import Rx-PSK of contact.""" c_code, onion_pub_key = separate_header(packet, CONFIRM_CODE_LENGTH) short_addr = pub_key_to_short_address(onion_pub_key) if not contact_list.has_pub_key(onion_pub_key): raise FunctionReturn(f"Error: Unknown account '{short_addr}'.", head_clear=True) contact = contact_list.get_contact_by_pub_key(onion_pub_key) psk_file = ask_path_gui(f"Select PSK for {contact.nick} ({short_addr})", settings, get_file=True) try: with open(psk_file, 'rb') as f: psk_data = f.read() except PermissionError: raise FunctionReturn("Error: No read permission for the PSK file.") if len(psk_data) != PSK_FILE_SIZE: raise FunctionReturn("Error: The PSK data in the file was invalid.", head_clear=True) salt, ct_tag = separate_header(psk_data, ARGON2_SALT_LENGTH) while True: try: password = MasterKey.get_password("PSK password") phase("Deriving the key decryption key", head=2) kdk = argon2_kdf(password, salt, time_cost=ARGON2_PSK_TIME_COST, memory_cost=ARGON2_PSK_MEMORY_COST) psk = auth_and_decrypt(ct_tag, kdk) phase(DONE) break except nacl.exceptions.CryptoError: print_on_previous_line() m_print("Invalid password. Try again.", head=1) print_on_previous_line(reps=5, delay=1) except (EOFError, KeyboardInterrupt): raise FunctionReturn("PSK import aborted.", head=2, delay=1, tail_clear=True) rx_mk, rx_hk = separate_header(psk, SYMMETRIC_KEY_LENGTH) if any(k == bytes(SYMMETRIC_KEY_LENGTH) for k in [rx_mk, rx_hk]): raise FunctionReturn("Error: Received invalid keys from contact.", head_clear=True) keyset = key_list.get_keyset(onion_pub_key) keyset.rx_mk = rx_mk keyset.rx_hk = rx_hk key_list.store_keys() contact.kex_status = KEX_STATUS_HAS_RX_PSK contact_list.store_contacts() # Pipes protects against shell injection. Source of command's parameter is # the program itself, and therefore trusted, but it's still good practice. subprocess.Popen(f"shred -n 3 -z -u {pipes.quote(psk_file)}", shell=True).wait() if os.path.isfile(psk_file): m_print( f"Warning! Overwriting of PSK ({psk_file}) failed. Press <Enter> to continue.", manual_proceed=True, box=True) message = f"Added Rx-side PSK for {contact.nick} ({short_addr})." local_win = window_list.get_local_window() local_win.add_new(ts, message) m_print([ message, '', "Warning!", "Physically destroy the keyfile transmission media ", "to ensure it does not steal data from this computer!", '', f"Confirmation code (to Transmitter): {c_code.hex()}" ], box=True, head=1, tail=1)
def test_get_path_to_file_gui(self): # Setup filedialog.askopenfilename = lambda title: 'test_path_to_file' # Test self.assertEqual(ask_path_gui('test message', self.settings, get_file=True), 'test_path_to_file')
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 Argon2d 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, time_cost=ARGON2_PSK_TIME_COST, memory_cost=ARGON2_PSK_MEMORY_COST) 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 command = (KEY_EX_PSK_TX + onion_pub_key + tx_mk + csprng() + tx_hk + csprng() + str_to_bytes(nick)) queue_command(command, settings, queues) 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 rxm_import(settings: 'Settings', queue_to_rxm: 'Queue') -> None: """Import encrypted file to RxM.""" f_path = ask_path_gui("Select file to import...", settings, get_file=True) with open(f_path, 'rb') as f: f_data = f.read() queue_to_rxm.put(IMPORTED_FILE_HEADER + f_data)
def nh_command( settings: 'Settings', q_to_nh: 'Queue', q_to_rxm: 'Queue', q_im_cmd: 'Queue', file_no: int # stdin input file descriptor ) -> None: """Process NH side commands.""" sys.stdin = os.fdopen(file_no) while True: try: if q_to_nh.empty(): time.sleep(0.001) continue command = q_to_nh.get() header = command[:2] if header in [UNENCRYPTED_SCREEN_CLEAR, UNENCRYPTED_SCREEN_RESET]: # Handle race condition with RxM command notification time.sleep(0.1) if settings.local_testing_mode and settings.data_diode_sockets: time.sleep(0.7) if header == UNENCRYPTED_SCREEN_CLEAR: q_im_cmd.put(command) clear_screen() if header == UNENCRYPTED_SCREEN_RESET: q_im_cmd.put(command) os.system('reset') if header == UNENCRYPTED_EXIT_COMMAND: exit() if header == UNENCRYPTED_EC_RATIO: value = eval(command[2:]) if not isinstance(value, int) or value < 1: c_print("Error: Received Invalid EC ratio value from TxM.") continue settings.e_correction_ratio = value settings.store_settings() c_print("Error correction ratio will change on restart.", head=1, tail=1) if header == UNENCRYPTED_BAUDRATE: value = eval(command[2:]) if not isinstance(value, int) or value not in serial.Serial.BAUDRATES: c_print( "Error: Received invalid baud rate value from TxM.") continue settings.serial_iface_speed = value settings.store_settings() c_print("Baud rate will change on restart.", head=1, tail=1) if header == UNENCRYPTED_IMPORT_COMMAND: f_path = ask_path_gui("Select file to import...", settings, get_file=True) with open(f_path, 'rb') as f: f_data = f.read() q_to_rxm.put(IMPORTED_FILE_CT_HEADER + f_data) if header == UNENCRYPTED_GUI_DIALOG: value = eval(command[2:]) settings.disable_gui_dialog = value settings.store_settings() c_print( "Changed setting disable_gui_dialog to {}.".format(value), head=1, tail=1) except (KeyboardInterrupt, EOFError, FunctionReturn): pass
def import_psk_rx_keys(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList', contact_list: 'ContactList', key_list: 'KeyList', settings: 'Settings') -> None: """Import Rx-PSK of contact.""" account = cmd_data.decode() if not contact_list.has_contact(account): raise FunctionReturn(f"Error: Unknown account '{account}'") contact = contact_list.get_contact(account) psk_file = ask_path_gui(f"Select PSK for {contact.nick}", settings, get_file=True) with open(psk_file, 'rb') as f: psk_data = f.read() if len(psk_data) != PSK_FILE_SIZE: raise FunctionReturn("Error: Invalid PSK data in file.") salt = psk_data[:ARGON2_SALT_LEN] ct_tag = psk_data[ARGON2_SALT_LEN:] while True: try: password = MasterKey.get_password("PSK password") phase("Deriving key decryption key", head=2) kdk, _ = argon2_kdf(password, salt, parallelism=1) psk_pt = auth_and_decrypt(ct_tag, key=kdk, soft_e=True) phase(DONE) break except nacl.exceptions.CryptoError: print_on_previous_line() c_print("Invalid password. Try again.", head=1) print_on_previous_line(reps=5, delay=1.5) except KeyboardInterrupt: raise FunctionReturn("PSK import aborted.", head=2) rx_key = psk_pt[0:32] rx_hek = psk_pt[32:64] if any(k == bytes(KEY_LENGTH) for k in [rx_key, rx_hek]): raise FunctionReturn("Error: Received invalid keys from contact.") keyset = key_list.get_keyset(account) keyset.rx_key = rx_key keyset.rx_hek = rx_hek key_list.store_keys() # Pipes protects against shell injection. Source of command's parameter # is user's own RxM and therefore trusted, but it's still good practice. subprocess.Popen(f"shred -n 3 -z -u {pipes.quote(psk_file)}", shell=True).wait() if os.path.isfile(psk_file): box_print( f"Warning! Overwriting of PSK ({psk_file}) failed. Press <Enter> to continue.", manual_proceed=True) local_win = window_list.get_local_window() message = f"Added Rx-PSK for {contact.nick} ({account})." local_win.add_new(ts, message) box_print([ message, '', "Warning!", "Physically destroy the keyfile transmission ", "media to ensure that no data escapes RxM!" ], head=1, tail=1)
def psk_import(cmd_data: bytes, ts: 'datetime', window_list: 'WindowList', contact_list: 'ContactList', key_list: 'KeyList', settings: 'Settings') -> None: """Import rx-PSK of contact.""" account = cmd_data.decode() if not contact_list.has_contact(account): raise FunctionReturn(f"Unknown account {account}.") contact = contact_list.get_contact(account) pskf = ask_path_gui(f"Select PSK for {contact.nick}", settings, get_file=True) with open(pskf, 'rb') as f: psk_data = f.read() if len( psk_data ) != 136: # Nonce (24) + Salt (32) + rx-key (32) + rx-hek (32) + tag (16) raise FunctionReturn("Invalid PSK data in file.") salt = psk_data[:32] ct_tag = psk_data[32:] while True: try: password = MasterKey.get_password("PSK password") phase("Deriving key decryption key", head=2) kdk, _ = argon2_kdf(password, salt, rounds=16, memory=128000, parallelism=1) psk_pt = auth_and_decrypt(ct_tag, key=kdk, soft_e=True) phase("Done") break except nacl.exceptions.CryptoError: print_on_previous_line() c_print("Invalid password. Try again.", head=1) print_on_previous_line(reps=5, delay=1.5) except KeyboardInterrupt: raise FunctionReturn("PSK import aborted.") rx_key = psk_pt[0:32] rx_hek = psk_pt[32:64] if rx_key == bytes(32) or rx_hek == bytes(32): raise FunctionReturn("Keys from contact are not valid.") keyset = key_list.get_keyset(account) keyset.rx_key = rx_key keyset.rx_hek = rx_hek key_list.store_keys() # Pipes protects against shell injection. Source of command # is trusted (user's own TxM) but it's still good practice. subprocess.Popen("shred -n 3 -z -u {}".format(pipes.quote(pskf)), shell=True).wait() if os.path.isfile(pskf): box_print(f"Warning! Overwriting of PSK ({pskf}) failed.") time.sleep(3) local_win = window_list.get_local_window() local_win.print_new(ts, f"Added Rx-PSK for {contact.nick} ({account})", print_=False) box_print([ f"Added Rx-PSK for {contact.nick}.", '', "Warning!", "Physically destroy the keyfile transmission ", "media to ensure that no data escapes RxM!" ], head=1, tail=1)
def create_pre_shared_key(account: str, user: str, nick: str, contact_list: 'ContactList', settings: 'Settings', queues: Dict[bytes, 'Queue']) -> None: """Generate new pre-shared key for manual key delivery. :param account: The contact's account name (e.g. [email protected]) :param user: The user's account name (e.g. [email protected]) :param nick: Nick of contact :param contact_list: Contact list object :param settings: Settings object :param queues: Dictionary of multiprocessing queues :return: None """ try: tx_key = csprng() tx_hek = csprng() salt = csprng() password = MasterKey.new_password("password for PSK") phase("Deriving key encryption key", head=2) kek, _ = argon2_kdf(password, salt, parallelism=1) phase(DONE) ct_tag = encrypt_and_sign(tx_key + tx_hek, key=kek) while True: store_d = ask_path_gui(f"Select removable media for {nick}", settings) f_name = f"{store_d}/{user}.psk - Give to {account}" try: with open(f_name, 'wb+') as f: f.write(salt + ct_tag) break except PermissionError: c_print("Error: Did not have permission to write to directory.") time.sleep(0.5) continue packet = KEY_EX_PSK_TX_HEADER \ + tx_key \ + tx_hek \ + account.encode() + US_BYTE + nick.encode() queue_command(packet, settings, queues[COMMAND_PACKET_QUEUE]) contact_list.add_contact(account, user, nick, bytes(FINGERPRINT_LEN), bytes(FINGERPRINT_LEN), settings.log_messages_by_default, settings.accept_files_by_default, settings.show_notifications_by_default) queues[KEY_MANAGEMENT_QUEUE].put((KDB_ADD_ENTRY_HEADER, account, tx_key, csprng(), tx_hek, csprng())) box_print(f"Successfully added {nick}.", head=1) clear_screen(delay=1) except KeyboardInterrupt: raise FunctionReturn("PSK generation aborted.", delay=1, head=2, tail_clear=True)