def test_invalid_size_key_from_argon2_raises_critical_error(self) -> None: with self.assertRaises(SystemExit): argon2_kdf(self.password, self.salt, ARGON2_MIN_TIME_COST, ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM) with self.assertRaises(SystemExit): argon2_kdf(self.password, self.salt, ARGON2_MIN_TIME_COST, ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM)
def test_invalid_length_salt_raises_critical_error(self) -> None: invalid_salts = [ salt_length * b'a' for salt_length in [0, ARGON2_SALT_LENGTH - 1, ARGON2_SALT_LENGTH + 1, 1000] ] for invalid_salt in invalid_salts: with self.assertRaises(SystemExit): argon2_kdf(self.password, invalid_salt, ARGON2_MIN_TIME_COST, ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM)
def new_master_key(self) -> None: """Create a new master key from salt and password.""" password = MasterKey.new_password() salt = csprng() rounds = ARGON2_ROUNDS memory = ARGON2_MIN_MEMORY phase("Deriving master key", head=2) while True: time_start = time.monotonic() master_key, parallellism = argon2_kdf(password, salt, rounds, memory=memory, local_test=self.local_test) time_final = time.monotonic() - time_start if time_final > 3.0: self.master_key = master_key ensure_dir(f'{DIR_USER_DATA}/') with open(self.file_name, 'wb+') as f: f.write(salt + hash_chain(self.master_key) + int_to_bytes(rounds) + int_to_bytes(memory) + int_to_bytes(parallellism)) phase(DONE) break else: memory *= 2
def test_valid_psk(self): # Setup packet = b'*****@*****.**' contact_list = ContactList(nicks=['Alice', 'local']) key_list = KeyList(nicks=['Alice', 'local']) keyset = key_list.get_keyset('*****@*****.**') keyset.rx_key = bytes(32) keyset.rx_hek = bytes(32) window_list = WindowList(nicks=['Alice', 'local']) ts = datetime.datetime.now() settings = Settings(disable_gui_dialog=True) o_input = builtins.input o_getpass = getpass.getpass builtins.input = lambda x: 'ut_psk' getpass.getpass = lambda x: 'testpassword' password = '******' salt = os.urandom(32) rx_key = os.urandom(32) rx_hek = os.urandom(32) kek, _ = argon2_kdf(password, salt, rounds=16, memory=128000, parallelism=1) ct_tag = encrypt_and_sign(rx_key + rx_hek, key=kek) with open('ut_psk', 'wb+') as f: f.write(salt + ct_tag) # Test self.assertTrue(os.path.isfile('ut_psk')) self.assertIsNone(psk_import(packet, ts, window_list, contact_list, key_list, settings)) self.assertFalse(os.path.isfile('ut_psk')) self.assertEqual(keyset.rx_key, rx_key) self.assertEqual(keyset.rx_hek, rx_hek) # Teardown builtins.input = o_input getpass.getpass = o_getpass
def new_master_key(self) -> None: """Create a new master key from salt and password. The number of rounds starts at 1 but is increased dynamically based on system performance. This allows more security on faster platforms without additional cost on key derivation time. """ password = MasterKey.new_password() salt = keygen() rounds = 1 phase("Deriving master key", head=2) while True: time_start = time.monotonic() master_key, memory = argon2_kdf(password, salt, rounds, local_testing=self.local_test) time_final = time.monotonic() - time_start if time_final > 3.0: self.master_key = master_key master_key_hash = hash_chain(master_key) ensure_dir(f'{DIR_USER_DATA}/') with open(self.file_name, 'wb+') as f: f.write(salt + master_key_hash + int_to_bytes(rounds) + int_to_bytes(memory)) phase('Done') break else: rounds *= 2
def test_argon2d_kdf(self): key = argon2_kdf('password', ARGON2_SALT_LENGTH * b'a', rounds=1, memory=100) self.assertIsInstance(key, bytes) self.assertEqual(len(key), SYMMETRIC_KEY_LENGTH)
def load_master_key(self) -> None: """Derive master key from password from stored values (salt, rounds, memory).""" ensure_dir(f'{DIR_USER_DATA}/') with open(self.file_name, 'rb') as f: data = f.read() salt = data[0:32] k_hash = data[32:64] rounds = bytes_to_int(data[64:72]) memory = bytes_to_int(data[72:80]) while True: password = MasterKey.get_password() phase("Deriving master key", head=2, offset=16) purp_key, _ = argon2_kdf(password, salt, rounds, memory, local_testing=self.local_test) if hash_chain(purp_key) == k_hash: phase("Password correct", done=True) self.master_key = purp_key clear_screen(delay=0.5) break else: phase("Invalid password", done=True) print_on_previous_line(reps=5, delay=1)
def test_valid_psk_overwrite_failure(self): # Setup keyset = self.key_list.get_keyset('*****@*****.**') keyset.rx_key = bytes(KEY_LENGTH) keyset.rx_hek = bytes(KEY_LENGTH) input_list = ['ut_psk', ''] gen = iter(input_list) builtins.input = lambda _: next(gen) subprocess.Popen = TestImportPSKRxKeys.MockPopen getpass.getpass = lambda _: 'testpassword' password = '******' salt = os.urandom(ARGON2_SALT_LEN) rx_key = os.urandom(KEY_LENGTH) rx_hek = os.urandom(KEY_LENGTH) kek, _ = argon2_kdf(password, salt, parallelism=1) ct_tag = encrypt_and_sign(rx_key + rx_hek, key=kek) with open('ut_psk', 'wb+') as f: f.write(salt + ct_tag) # Test self.assertTrue(os.path.isfile('ut_psk')) self.assertIsNone(import_psk_rx_keys(self.packet, self.ts, self.window_list, self.contact_list, self.key_list, self.settings)) self.assertTrue(os.path.isfile('ut_psk')) self.assertEqual(keyset.rx_key, rx_key) self.assertEqual(keyset.rx_hek, rx_hek)
def test_valid_psk_overwrite_failure(self, *_: Any) -> None: # Setup keyset = self.key_list.get_keyset(nick_to_pub_key("Alice")) keyset.rx_mk = bytes(SYMMETRIC_KEY_LENGTH) keyset.rx_hk = bytes(SYMMETRIC_KEY_LENGTH) salt = os.urandom(ARGON2_SALT_LENGTH) rx_key = os.urandom(SYMMETRIC_KEY_LENGTH) rx_hek = os.urandom(SYMMETRIC_KEY_LENGTH) kek = argon2_kdf('test_password', salt, time_cost=1, memory_cost=100, parallelism=1) ct_tag = encrypt_and_sign(rx_key + rx_hek, key=kek) with open(self.file_name, 'wb+') as f: f.write(salt + ct_tag) # Test self.assertTrue(os.path.isfile(self.file_name)) self.assertIsNone(key_ex_psk_rx(*self.args)) self.assertTrue(os.path.isfile(self.file_name)) self.assertEqual(keyset.rx_mk, rx_key) self.assertEqual(keyset.rx_hk, rx_hek)
def test_argon2_kdf_local_testing(self): key, parallelism = argon2_kdf('password', ARGON2_SALT_LEN * b'a', local_test=True) self.assertIsInstance(key, bytes) self.assertEqual(len(key), KEY_LENGTH) self.assertEqual(parallelism, max(multiprocessing.cpu_count() // 2, 1))
def load_master_key(self) -> bytes: """Derive the master key from password and salt. Load the salt, hash, and key derivation settings from the login database. Derive the purported master key from the salt and entered password. If the BLAKE2b hash of derived master key matches the hash in the login database, accept the derived master key. """ database_data = self.database.load_database() if len(database_data) != MASTERKEY_DB_SIZE: raise CriticalError(f"Invalid {self.file_name} database size.") salt, key_hash, time_bytes, memory_bytes, parallelism_bytes \ = separate_headers(database_data, [ARGON2_SALT_LENGTH, BLAKE2_DIGEST_LENGTH, ENCODED_INTEGER_LENGTH, ENCODED_INTEGER_LENGTH]) time_cost = bytes_to_int(time_bytes) memory_cost = bytes_to_int(memory_bytes) parallelism = bytes_to_int(parallelism_bytes) while True: password = MasterKey.get_password() phase("Deriving master key", head=2, offset=len("Password correct")) purp_key = argon2_kdf(password, salt, time_cost, memory_cost, parallelism) if blake2b(purp_key) == key_hash: phase("Password correct", done=True, delay=1) clear_screen() return purp_key phase("Invalid password", done=True, delay=1) print_on_previous_line(reps=5)
def test_local_testing_sanity_check(self): key, mem = argon2_kdf("test_password", salt=bytes(32), rounds=1, local_testing=True) self.assertIsInstance(key, bytes) self.assertIsInstance(mem, int)
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 timed_key_derivation(password: str, salt: bytes, time_cost: int, memory_cost: int, parallelism: int) -> Tuple[bytes, float]: """Derive key and measure its derivation time.""" time_start = time.monotonic() master_key = argon2_kdf(password, salt, time_cost, memory_cost, parallelism) kd_time = time.monotonic() - time_start return master_key, kd_time
def test_sanity_check(self): key, mem = argon2_kdf("test_password", salt=bytes(32), rounds=1, memory=128000, parallelism=1) self.assertEqual(mem, 128000) self.assertEqual( key.hex(), "73883b6b2ea60d0adf27fb52e1f41af4" "29bfe8a0d79ae4a2f87be6c4d73e6a11")
def new_master_key(self) -> bytes: """Create a new master key from password and salt. The generated master key depends on a 256-bit salt and the password entered by the user. Additional computational strength is added by the slow hash function (Argon2d). This method automatically tweaks the Argon2 memory parameter so that key derivation on used hardware takes at least three seconds. The more cores and the faster each core is, the more security a given password provides. The preimage resistance of BLAKE2b prevents derivation of master key from the stored hash, and Argon2d ensures brute force and dictionary attacks against the master password are painfully slow even with GPUs/ASICs/FPGAs, as long as the password is sufficiently strong. The salt does not need additional protection as the security it provides depends on the salt space in relation to the number of attacked targets (i.e. if two or more physically compromised systems happen to share the same salt, the attacker can speed up the attack against those systems with time-memory-trade-off attack). A 256-bit salt ensures that even in a group of 4.8*10^29 users, the probability that two users share the same salt is just 10^(-18).* * https://en.wikipedia.org/wiki/Birthday_attack """ password = MasterKey.new_password() salt = csprng(ARGON2_SALT_LENGTH) memory = ARGON2_MIN_MEMORY parallelism = multiprocessing.cpu_count() if self.local_test: parallelism = max(1, parallelism // 2) phase("Deriving master key", head=2) while True: time_start = time.monotonic() master_key = argon2_kdf(password, salt, ARGON2_ROUNDS, memory, parallelism) kd_time = time.monotonic() - time_start if kd_time < MIN_KEY_DERIVATION_TIME: memory *= 2 else: ensure_dir(DIR_USER_DATA) with open(self.file_name, 'wb+') as f: f.write(salt + blake2b(master_key) + int_to_bytes(memory) + int_to_bytes(parallelism)) phase(DONE) return master_key
def test_invalid_keys_raise_fr(self, *_): # Setup keyset = self.key_list.get_keyset(nick_to_pub_key("Alice")) keyset.rx_mk = bytes(SYMMETRIC_KEY_LENGTH) keyset.rx_hk = bytes(SYMMETRIC_KEY_LENGTH) salt = bytes(ARGON2_SALT_LENGTH) rx_key = bytes(SYMMETRIC_KEY_LENGTH) rx_hek = bytes(SYMMETRIC_KEY_LENGTH) kek = argon2_kdf('password', salt, time_cost=1, memory_cost=100, parallelism=1) ct_tag = encrypt_and_sign(rx_key + rx_hek, key=kek) with open(self.file_name, 'wb+') as f: f.write(salt + ct_tag) # Test self.assert_fr("Error: Received invalid keys from contact.", key_ex_psk_rx, *self.args)
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 test_invalid_keys_raise_fr(self): # Setup keyset = self.key_list.get_keyset('*****@*****.**') keyset.rx_key = bytes(KEY_LENGTH) keyset.rx_hek = bytes(KEY_LENGTH) password = '******' input_list = ['bad', password] gen = iter(input_list) getpass.getpass = lambda _: str(next(gen)) salt = os.urandom(ARGON2_SALT_LEN) rx_key = bytes(KEY_LENGTH) rx_hek = os.urandom(KEY_LENGTH) kek, _ = argon2_kdf(password, salt, parallelism=1) ct_tag = encrypt_and_sign(rx_key + rx_hek, key=kek) with open('ut_psk', 'wb+') as f: f.write(salt + ct_tag) # Test self.assertFR("Error: Received invalid keys from contact.", import_psk_rx_keys, self.packet, self.ts, self.window_list, self.contact_list, self.key_list, self.settings)
def test_invalid_keys_raise_fr(self): # Setup packet = b'*****@*****.**' contact_list = ContactList(nicks=['Alice', 'local']) key_list = KeyList(nicks=['Alice', 'local']) keyset = key_list.get_keyset('*****@*****.**') keyset.rx_key = bytes(32) keyset.rx_hek = bytes(32) window_list = WindowList(nicks=['Alice', 'local']) ts = datetime.datetime.now() settings = Settings(disable_gui_dialog=True) o_input = builtins.input o_getpass = getpass.getpass builtins.input = lambda x: 'ut_psk' input_list = ['bad', 'testpassword'] gen = iter(input_list) def mock_input(_): return str(next(gen)) getpass.getpass = mock_input password = '******' salt = os.urandom(32) rx_key = bytes(32) rx_hek = os.urandom(32) kek, _ = argon2_kdf(password, salt, rounds=16, memory=128000, parallelism=1) ct_tag = encrypt_and_sign(rx_key + rx_hek, key=kek) with open('ut_psk', 'wb+') as f: f.write(salt + ct_tag) # Test self.assertFR("Keys from contact are not valid.", psk_import, packet, ts, window_list, contact_list, key_list, settings) # Teardown os.remove('ut_psk') builtins.input = o_input getpass.getpass = o_getpass
def new_master_key(self): password = MasterKey.new_password() salt = os.urandom(32) rounds = 1 assert isinstance(salt, bytes) while True: time_start = time.monotonic() master_key, memory = argon2_kdf(password, salt, rounds, local_testing=False) time_final = time.monotonic() - time_start if time_final > 3.0: self.master_key = master_key master_key_hash = hash_chain(master_key) ensure_dir(f'{DIR_USER_DATA}/') with open(self.file_name, 'wb+') as f: f.write(salt + master_key_hash + int_to_bytes(rounds) + int_to_bytes(memory)) break else: rounds *= 2
def test_valid_psk(self): # Setup keyset = self.key_list.get_keyset('*****@*****.**') keyset.rx_key = bytes(KEY_LENGTH) keyset.rx_hek = bytes(KEY_LENGTH) getpass.getpass = lambda _: 'testpassword' password = '******' salt = os.urandom(ARGON2_SALT_LEN) rx_key = os.urandom(KEY_LENGTH) rx_hek = os.urandom(KEY_LENGTH) kek, _ = argon2_kdf(password, salt, parallelism=1) ct_tag = encrypt_and_sign(rx_key + rx_hek, key=kek) with open('ut_psk', 'wb+') as f: f.write(salt + ct_tag) # Test self.assertTrue(os.path.isfile('ut_psk')) self.assertIsNone(import_psk_rx_keys(self.packet, self.ts, self.window_list, self.contact_list, self.key_list, self.settings)) self.assertFalse(os.path.isfile('ut_psk')) self.assertEqual(keyset.rx_key, rx_key) self.assertEqual(keyset.rx_hek, rx_hek)
def load_master_key(self) -> None: """Derive master key from password and salt.""" with open(self.file_name, 'rb') as f: data = f.read() salt = data[0:32] key_hash = data[32:64] rounds = bytes_to_int(data[64:72]) memory = bytes_to_int(data[72:80]) parallelism = bytes_to_int(data[80:88]) while True: password = MasterKey.get_password() phase("Deriving master key", head=2, offset=16) purp_key, _ = argon2_kdf(password, salt, rounds, memory, parallelism) if hash_chain(purp_key) == key_hash: self.master_key = purp_key phase("Password correct", done=True) clear_screen(delay=0.5) break else: phase("Invalid password", done=True) print_on_previous_line(reps=5, delay=1)
def test_too_small_parallelism_raises_critical_error(self) -> None: with self.assertRaises(SystemExit): argon2_kdf(self.password, self.salt, ARGON2_MIN_TIME_COST, ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM - 1)
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) store_keys_on_removable_drive(ct_tag, salt, nick, onion_pub_key, onion_service, settings) deliver_contact_data(KEY_EX_PSK_TX, nick, onion_pub_key, tx_mk, csprng(), tx_hk, csprng(), queues, settings) 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 SoftError("PSK generation aborted.", tail_clear=True, delay=1, head=2)
def test_invalid_salt_length_raises_critical_error(self): for salt_length in [v for v in range(1000) if v != ARGON2_SALT_LENGTH]: with self.assertRaises(SystemExit): argon2_kdf('password', salt_length * b'a')
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 test_argon2_kdf_key_type_and_length(self) -> None: key = argon2_kdf(self.password, self.salt, ARGON2_MIN_TIME_COST, ARGON2_MIN_MEMORY_COST, ARGON2_MIN_PARALLELISM) self.assertIsInstance(key, bytes) self.assertEqual(len(key), SYMMETRIC_KEY_LENGTH)
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_argon2_kdf(self): key, parallelism = argon2_kdf('password', ARGON2_SALT_LEN * b'a') self.assertIsInstance(key, bytes) self.assertEqual(len(key), KEY_LENGTH) self.assertEqual(parallelism, multiprocessing.cpu_count())