def decrypt_rx_psk(ct_tag: bytes, salt: bytes) -> bytes: """Get PSK password from user and decrypt Rx-PSK.""" while True: try: password = MasterKey.get_password("PSK password") phase("Deriving the key decryption key", head=2) kdk = argon2_kdf(password, salt, ARGON2_PSK_TIME_COST, ARGON2_PSK_MEMORY_COST, ARGON2_PSK_PARALLELISM) psk = auth_and_decrypt(ct_tag, kdk) phase(DONE) return psk 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 SoftError("PSK import aborted.", head=2, delay=1, tail_clear=True)
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 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)