def test_auth_and_decrypt_with_the_official_test_vectors(self) -> None: self.assertEqual( auth_and_decrypt(self.nonce_ct_tag_ietf, self.key, ad=self.ad), self.plaintext) self.assertEqual( auth_and_decrypt(self.nonce_ct_tag_libsodium, self.key, ad=self.ad), self.plaintext)
def test_auth_and_decrypt_with_official_test_vectors(self): self.assertEqual( auth_and_decrypt(self.nonce_ct_tag, self.key, ad=self.ad), self.plaintext) self.assertEqual( auth_and_decrypt(self.ietf_nonce_ct_tag, self.key, ad=self.ad), self.plaintext)
def decrypt_assembly_packet(packet: bytes, # Assembly packet ciphertext onion_pub_key: bytes, # Onion Service pubkey of associated contact origin: bytes, # Direction of packet window_list: 'WindowList', # WindowList object contact_list: 'ContactList', # ContactList object key_list: 'KeyList' # Keylist object ) -> bytes: # Decrypted assembly packet """Decrypt assembly packet from contact/local Transmitter.""" ct_harac, ct_assemby_packet = separate_header(packet, header_length=HARAC_CT_LENGTH) local_window = window_list.get_local_window() command = onion_pub_key == LOCAL_PUBKEY p_type = "command" if command else "packet" direction = "from" if command or (origin == ORIGIN_CONTACT_HEADER) else "sent to" nick = contact_list.get_contact_by_pub_key(onion_pub_key).nick # Load keys keyset = key_list.get_keyset(onion_pub_key) key_dir = TX if origin == ORIGIN_USER_HEADER else RX header_key = getattr(keyset, f'{key_dir}_hk') # type: bytes message_key = getattr(keyset, f'{key_dir}_mk') # type: bytes if any(k == bytes(SYMMETRIC_KEY_LENGTH) for k in [header_key, message_key]): raise FunctionReturn("Warning! Loaded zero-key for packet decryption.") # Decrypt hash ratchet counter try: harac_bytes = auth_and_decrypt(ct_harac, header_key) except nacl.exceptions.CryptoError: raise FunctionReturn( f"Warning! Received {p_type} {direction} {nick} had an invalid hash ratchet MAC.", window=local_window) # Catch up with hash ratchet offset purp_harac = bytes_to_int(harac_bytes) stored_harac = getattr(keyset, f'{key_dir}_harac') offset = purp_harac - stored_harac if offset < 0: raise FunctionReturn( f"Warning! Received {p_type} {direction} {nick} had an expired hash ratchet counter.", window=local_window) process_offset(offset, origin, direction, nick, local_window) for harac in range(stored_harac, stored_harac + offset): message_key = blake2b(message_key + int_to_bytes(harac), digest_size=SYMMETRIC_KEY_LENGTH) # Decrypt packet try: assembly_packet = auth_and_decrypt(ct_assemby_packet, message_key) except nacl.exceptions.CryptoError: raise FunctionReturn(f"Warning! Received {p_type} {direction} {nick} had an invalid MAC.", window=local_window) # Update message key and harac keyset.update_mk(key_dir, blake2b(message_key + int_to_bytes(stored_harac + offset), digest_size=SYMMETRIC_KEY_LENGTH), offset + 1) return assembly_packet
def test_invalid_size_key_raises_critical_error(self) -> None: invalid_keys = [ key_length * b'a' for key_length in [1, SYMMETRIC_KEY_LENGTH - 1, SYMMETRIC_KEY_LENGTH + 1, 1000] ] for invalid_key in invalid_keys: with self.assertRaises(SystemExit): encrypt_and_sign(self.libsodium_plaintext, invalid_key) with self.assertRaises(SystemExit): auth_and_decrypt(self.nonce_ct_tag_ietf, invalid_key)
def decrypt_assembly_packet(packet: bytes, window_list: 'WindowList', contact_list: 'ContactList', key_list: 'KeyList') -> Tuple[bytes, str, bytes]: """Decrypt assembly packet from contact/local TxM.""" enc_harac = packet[1:49] enc_msg = packet[49:345] window = window_list.get_local_window() origin, direction, key_dir, p_type, account, nick = get_packet_values( packet, window, contact_list) # Load keys keyset = key_list.get_keyset(account) header_key = getattr(keyset, f'{key_dir}_hek') message_key = getattr(keyset, f'{key_dir}_key') if any(k == bytes(KEY_LENGTH) for k in [header_key, message_key]): raise FunctionReturn("Warning! Loaded zero-key for packet decryption.") # Decrypt hash ratchet counter try: harac_bytes = auth_and_decrypt(enc_harac, header_key, soft_e=True) except nacl.exceptions.CryptoError: raise FunctionReturn( f"Warning! Received {p_type} {direction} {nick} had an invalid hash ratchet MAC.", window=window) # Catch up with hash ratchet offset purp_harac = bytes_to_int(harac_bytes) stored_harac = getattr(keyset, f'{key_dir}_harac') offset = purp_harac - stored_harac if offset < 0: raise FunctionReturn( f"Warning! Received {p_type} {direction} {nick} had an expired hash ratchet counter.", window=window) process_offset(offset, origin, direction, nick, window) for _ in range(offset): message_key = hash_chain(message_key) # Decrypt packet try: assembly_packet = auth_and_decrypt(enc_msg, message_key, soft_e=True) except nacl.exceptions.CryptoError: raise FunctionReturn( f"Warning! Received {p_type} {direction} {nick} had an invalid MAC.", window=window) # Update keys in database keyset.update_key(key_dir, hash_chain(message_key), offset + 1) return assembly_packet, account, origin
def change_log_db_key(previous_key: bytes, new_key: bytes, settings: 'Settings') -> None: """Re-encrypt log database with a new master key.""" ensure_dir(DIR_USER_DATA) file_name = f'{DIR_USER_DATA}{settings.software_operation}_logs' temp_name = f'{file_name}_temp' if not os.path.isfile(file_name): raise FunctionReturn("Error: Could not find log database.") if os.path.isfile(temp_name): os.remove(temp_name) f_old = open(file_name, 'rb') f_new = open(temp_name, 'ab+') for ct in iter(lambda: f_old.read(LOG_ENTRY_LENGTH), b''): pt = auth_and_decrypt(ct, key=previous_key, database=file_name) f_new.write(encrypt_and_sign(pt, key=new_key)) f_old.close() f_new.close() os.remove(file_name) os.rename(temp_name, file_name)
def process_local_key(packet: bytes, contact_list: 'ContactList', key_list: 'KeyList') -> None: """Decrypt local key packet, add local contact/keyset.""" try: clear_screen() box_print(["Received encrypted local key"], tail=1) kdk = get_b58_key('localkey') try: pt = auth_and_decrypt(packet[1:], key=kdk, soft_e=True) except nacl.exceptions.CryptoError: raise FunctionReturn("Invalid key decryption key.", delay=1.5) key = pt[0:32] hek = pt[32:64] conf_code = pt[64:65] # Add local contact to contact list database contact_list.add_contact('local', 'local', 'local', bytes(32), bytes(32), False, False, True) # Add local contact to keyset database key_list.add_keyset('local', key, bytes(32), hek, bytes(32)) box_print([f"Confirmation code for TxM: {conf_code.hex()}"], head=1) except KeyboardInterrupt: raise FunctionReturn("Local key setup aborted.", delay=1)
def _load_keys(self) -> None: """Load KeySets from the encrypted database. This function first reads and decrypts the database content. It then splits the plaintext into a list of 176-byte blocks. Each block contains the serialized data of one KeySet. Next, the function will remove from the list all dummy KeySets (that start with the `dummy_id` byte string). The function will then populate the `self.keysets` list with KeySet objects, the data of which is sliced and decoded from the dummy-free blocks. """ with open(self.file_name, 'rb') as f: ct_bytes = f.read() pt_bytes = auth_and_decrypt(ct_bytes, self.master_key.master_key, database=self.file_name) blocks = split_byte_string(pt_bytes, item_len=KEYSET_LENGTH) df_blocks = [b for b in blocks if not b.startswith(self.dummy_id)] for block in df_blocks: if len(block) != KEYSET_LENGTH: raise CriticalError("Invalid data in key database.") onion_pub_key, tx_mk, rx_mk, tx_hk, rx_hk, tx_harac_bytes, rx_harac_bytes \ = separate_headers(block, [ONION_SERVICE_PUBLIC_KEY_LENGTH] + 4*[SYMMETRIC_KEY_LENGTH] + [HARAC_LENGTH]) self.keysets.append(KeySet(onion_pub_key=onion_pub_key, tx_mk=tx_mk, rx_mk=rx_mk, tx_hk=tx_hk, rx_hk=rx_hk, tx_harac=bytes_to_int(tx_harac_bytes), rx_harac=bytes_to_int(rx_harac_bytes), store_keys=self.store_keys))
def load_database(self) -> bytes: """Load data from database. This function first checks if a temporary file exists from previous session. The integrity of the temporary file is verified with the Poly1305 MAC before the database is replaced. The function then reads the up-to-date database content and decrypts it. """ if os.path.isfile(self.database_temp): if self.verify_file(self.database_temp): os.replace(self.database_temp, self.database_name) else: # If temp file is not authentic, the file is most likely corrupt, so # we delete it and continue using the old file to ensure atomicity. os.remove(self.database_temp) with open(self.database_name, 'rb') as f: database_data = f.read() return auth_and_decrypt(database_data, self.database_key, database=self.database_name)
def __iter__(self) -> Iterator[bytes]: """Iterate over encrypted log entries.""" for log_entry in self.c.execute("SELECT log_entry FROM log_entries"): plaintext = auth_and_decrypt(log_entry[0], self.database_key, database=self.database_name) yield plaintext
def re_encrypt(previous_key: bytes, new_key: bytes, settings: 'Settings') -> None: """Re-encrypt database with a new master key.""" ensure_dir(f'{DIR_USER_DATA}/') file_name = f'{DIR_USER_DATA}/{settings.software_operation}_logs' temp_name = f'{DIR_USER_DATA}/{settings.software_operation}_logs_temp' if not os.path.isfile(file_name): raise FunctionReturn(f"Error: Could not find '{file_name}'.") if os.path.isfile(temp_name): os.remove(temp_name) f_old = open(file_name, 'rb') f_new = open(temp_name, 'ab+') def read_entry(): """Read log entry.""" return f_old.read(1325) for ct_old in iter(read_entry, b''): pt_new = auth_and_decrypt(ct_old, key=previous_key) f_new.write(encrypt_and_sign(pt_new, key=new_key)) f_old.close() f_new.close() os.remove(file_name) os.rename(temp_name, file_name)
def decrypt_and_store_file( ts: 'datetime', # Timestamp of received packet file_ct: bytes, # File ciphertext file_key: bytes, # File decryption key file_name: str, # Name of the file onion_pub_key: bytes, # Onion Service pubkey of sender nick: str, # Nickname of sender window_list: 'WindowList', # WindowList object settings: 'Settings' # Settings object ) -> None: """Decrypt and store file.""" try: file_pt = auth_and_decrypt(file_ct, file_key) except nacl.exceptions.CryptoError: raise SoftError("Error: Decryption of file data failed.") try: file_dc = decompress(file_pt, settings.max_decompress_size) except zlib.error: raise SoftError("Error: Decompression of file data failed.") file_dir = f'{DIR_RECV_FILES}{nick}/' final_name = store_unique(file_dc, file_dir, file_name) message = f"Stored file from {nick} as '{final_name}'." if settings.traffic_masking and window_list.active_win is not None: window = window_list.active_win else: window = window_list.get_window(onion_pub_key) window.add_new(ts, message, onion_pub_key, output=True, event_msg=True)
def get_file( purp_url_token: str, queues: 'QueueDict', pub_key_dict: 'PubKeyDict', buf_key: bytes, ) -> Any: """Send queued files to contact.""" if not validate_url_token(purp_url_token, queues, pub_key_dict): return '' identified_onion_pub_key = pub_key_dict[purp_url_token] sub_dir = hashlib.blake2b(identified_onion_pub_key, key=buf_key, digest_size=BLAKE2_DIGEST_LENGTH).hexdigest() buf_dir = f"{RELAY_BUFFER_OUTGOING_F_DIR}/{sub_dir}/" ensure_dir(buf_dir) if len(os.listdir(buf_dir)) > 0: packet_ct, db = read_buffer_file(buf_dir, RELAY_BUFFER_OUTGOING_FILE) packet = auth_and_decrypt(packet_ct, key=buf_key, database=f"{buf_dir}{db}") mem = BytesIO() mem.write(packet) mem.seek(0) return send_file(mem, mimetype="application/octet-stream") return ''
def decrypt_local_key(ts: 'datetime', packet: bytes, kdk_hashes: List[bytes], packet_hashes: List[bytes], settings: 'Settings', l_queue: 'local_key_queue') -> Tuple['datetime', bytes]: """Decrypt local key packet.""" while True: kdk = get_b58_key(B58_LOCAL_KEY, settings) kdk_hash = blake2b(kdk) # Check if the key was an old one. if kdk_hash in kdk_hashes: m_print("Error: Entered an old local key decryption key.", delay=1) continue try: plaintext = auth_and_decrypt(packet, kdk) except nacl.exceptions.CryptoError: ts, plaintext = process_local_key_buffer(kdk, l_queue) protect_kdk(kdk) # Cache hashes needed to recognize reissued local key packets and key decryption keys. kdk_hashes.append(kdk_hash) packet_hashes.append(blake2b(packet)) return ts, plaintext
def load_settings(self) -> None: """Load settings from the encrypted database.""" with open(self.file_name, 'rb') as f: ct_bytes = f.read() pt_bytes = auth_and_decrypt(ct_bytes, self.master_key.master_key, database=self.file_name) # Update settings based on plaintext byte string content for key in self.key_list: attribute = self.__getattribute__(key) if isinstance(attribute, bool): value = bytes_to_bool( pt_bytes[0]) # type: Union[bool, int, float] pt_bytes = pt_bytes[ENCODED_BOOLEAN_LENGTH:] elif isinstance(attribute, int): value = bytes_to_int(pt_bytes[:ENCODED_INTEGER_LENGTH]) pt_bytes = pt_bytes[ENCODED_INTEGER_LENGTH:] elif isinstance(attribute, float): value = bytes_to_double(pt_bytes[:ENCODED_FLOAT_LENGTH]) pt_bytes = pt_bytes[ENCODED_FLOAT_LENGTH:] else: raise CriticalError( "Invalid data type in settings default values.") setattr(self, key, value)
def get_message(purp_url_token: str, queues: 'QueueDict', pub_key_dict: 'PubKeyDict', buf_key: bytes) -> str: """Send queued messages to contact.""" if not validate_url_token(purp_url_token, queues, pub_key_dict): return '' identified_onion_pub_key = pub_key_dict[purp_url_token] # Load outgoing messages for all contacts, # return the oldest message for contact sub_dir = hashlib.blake2b(identified_onion_pub_key, key=buf_key, digest_size=BLAKE2_DIGEST_LENGTH).hexdigest() buf_dir = f"{RELAY_BUFFER_OUTGOING_M_DIR}/{sub_dir}/" ensure_dir(buf_dir) packets = [] while len(os.listdir(buf_dir)) > 0: packet_ct, db = read_buffer_file(buf_dir, RELAY_BUFFER_OUTGOING_MESSAGE) packet = auth_and_decrypt(packet_ct, key=buf_key, database=f"{buf_dir}{db}") packets.append(packet.decode()) if packets: all_message_packets = '\n'.join(packets) return all_message_packets return ''
def process_local_key_buffer( kdk: bytes, l_queue: 'local_key_queue') -> Tuple[datetime, bytes]: """Check if the KDK was for a packet further ahead in the queue.""" buffer = [] # type: List[Tuple[datetime, bytes]] while l_queue.qsize() > 0: tup = l_queue.get() # type: Tuple[datetime, bytes] if tup not in buffer: buffer.append(tup) for i, tup in enumerate(buffer): try: plaintext = auth_and_decrypt(tup[1], kdk) # If we reach this point, decryption was successful. for unexamined in buffer[i + 1:]: l_queue.put(unexamined) buffer = [] ts = tup[0] return ts, plaintext except nacl.exceptions.CryptoError: continue # Finished the buffer without finding local key CT # for the kdk. Maybe the kdk is from another session. raise SoftError("Error: Incorrect key decryption key.", delay=1)
def verify_file(self, database_name: str) -> bool: """Verify integrity of database file content.""" conn = sqlite3.connect(database_name) c = conn.cursor() try: log_entries = c.execute("SELECT log_entry FROM log_entries") except sqlite3.DatabaseError: return False for ct_log_entry in log_entries: try: auth_and_decrypt(ct_log_entry[0], self.database_key) except nacl.exceptions.CryptoError: return False return True
def verify_file(self, database_name: str) -> bool: """Verify integrity of file content.""" with open(database_name, 'rb') as f: purp_data = f.read() try: _ = auth_and_decrypt(purp_data, self.database_key) return True except nacl.exceptions.CryptoError: return False
def load_onion_service_private_key(self) -> bytes: """Load the Onion Service private key from the encrypted database.""" with open(self.file_name, 'rb') as f: ct_bytes = f.read() onion_private_key = auth_and_decrypt(ct_bytes, self.master_key.master_key, database=self.file_name) if len(onion_private_key) != ONION_SERVICE_PRIVATE_KEY_LENGTH: raise CriticalError("Invalid Onion Service private key length.") return onion_private_key
def process_local_key(ts: 'datetime', packet: bytes, window_list: 'WindowList', contact_list: 'ContactList', key_list: 'KeyList', settings: 'Settings') -> None: """Decrypt local key packet and add local contact/keyset.""" bootstrap = not key_list.has_local_key() try: while True: clear_screen() box_print("Received encrypted local key", tail=1) kdk = get_b58_key(B58_LOCAL_KEY, settings) try: pt = auth_and_decrypt(packet[1:], key=kdk, soft_e=True) break except nacl.exceptions.CryptoError: if bootstrap: raise FunctionReturn( "Error: Incorrect key decryption key.", delay=1.5) c_print("Incorrect key decryption key.", head=1) clear_screen(delay=1.5) key = pt[0:32] hek = pt[32:64] conf_code = pt[64:65] # 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, True) # Add local keyset to keyset database key_list.add_keyset(rx_account=LOCAL_ID, tx_key=key, rx_key=csprng(), tx_hek=hek, rx_hek=csprng()) box_print(f"Confirmation code for TxM: {conf_code.hex()}", head=1) local_win = window_list.get_local_window() local_win.add_new(ts, "Added new local key.") if bootstrap: window_list.active_win = local_win except KeyboardInterrupt: raise FunctionReturn("Local key setup aborted.", delay=1, head=3, tail_clear=True)
def assemble_message_packet(self) -> bytes: """Assemble message packet.""" padded = b''.join([p[ASSEMBLY_PACKET_HEADER_LENGTH:] for p in self.assembly_pt_list]) payload = rm_padding_bytes(padded) if len(self.assembly_pt_list) > 1: msg_ct, msg_key = separate_trailer(payload, SYMMETRIC_KEY_LENGTH) try: payload = auth_and_decrypt(msg_ct, msg_key) except nacl.exceptions.CryptoError: raise FunctionReturn("Error: Decryption of message failed.") try: return decompress(payload, MAX_MESSAGE_SIZE) except zlib.error: raise FunctionReturn("Error: Decompression of message failed.")
def process_assembled_file( ts: 'datetime', # Timestamp last received packet payload: bytes, # File name and content onion_pub_key: bytes, # Onion Service pubkey of sender nick: str, # Nickname of sender settings: 'Settings', # Settings object window_list: 'WindowList', # WindowList object ) -> None: """Process received file assembly packets.""" try: file_name_b, file_data = payload.split(US_BYTE, 1) except ValueError: raise FunctionReturn("Error: Received file had an invalid structure.") try: file_name = file_name_b.decode() except UnicodeError: raise FunctionReturn( "Error: Received file name had an invalid encoding.") if not file_name.isprintable() or not file_name or '/' in file_name: raise FunctionReturn("Error: Received file had an invalid name.") file_ct, file_key = separate_trailer(file_data, SYMMETRIC_KEY_LENGTH) if len(file_key) != SYMMETRIC_KEY_LENGTH: raise FunctionReturn("Error: Received file had an invalid key.") try: file_pt = auth_and_decrypt(file_ct, file_key) except nacl.exceptions.CryptoError: raise FunctionReturn("Error: Decryption of file data failed.") try: file_dc = decompress(file_pt, settings.max_decompress_size) except zlib.error: raise FunctionReturn("Error: Decompression of file data failed.") file_dir = f'{DIR_RECV_FILES}{nick}/' final_name = store_unique(file_dc, file_dir, file_name) message = f"Stored file from {nick} as '{final_name}'." if settings.traffic_masking and window_list.active_win is not None: window = window_list.active_win else: window = window_list.get_window(onion_pub_key) window.add_new(ts, message, onion_pub_key, output=True, event_msg=True)
def process_imported_file(ts: 'datetime', packet: bytes, window_list: 'WindowList', settings: 'Settings'): """Decrypt and store imported file.""" while True: try: print('') key = get_b58_key(B58_FILE_KEY, settings) except KeyboardInterrupt: raise FunctionReturn("File import aborted.", head=2) try: phase("Decrypting file", head=1) file_pt = auth_and_decrypt(packet[1:], key, soft_e=True) phase(DONE) break except (nacl.exceptions.CryptoError, nacl.exceptions.ValueError): phase('ERROR', done=True) c_print("Invalid decryption key. Try again.") print_on_previous_line(reps=7, delay=1.5) except KeyboardInterrupt: phase('ABORT', done=True) raise FunctionReturn("File import aborted.") try: phase("Decompressing file") file_dc = zlib.decompress(file_pt) phase(DONE) except zlib.error: phase('ERROR', done=True) raise FunctionReturn("Error: Decompression of file data failed.") try: f_name = bytes_to_str(file_dc[:PADDED_UTF32_STR_LEN]) except UnicodeError: raise FunctionReturn("Error: Received file name had invalid encoding.") if not f_name.isprintable() or not f_name: raise FunctionReturn("Error: Received file had an invalid name.") f_data = file_dc[PADDED_UTF32_STR_LEN:] final_name = store_unique(f_data, DIR_IMPORTED, f_name) message = f"Stored imported file as '{final_name}'" box_print(message, head=1) local_win = window_list.get_local_window() local_win.add_new(ts, message)
def process_file( ts: 'datetime', # Timestamp of received_packet onion_pub_key: bytes, # Onion Service pubkey of sender file_ct: bytes, # File ciphertext file_key: bytes, # File decryption key contact_list: 'ContactList', # ContactList object window_list: 'WindowList', # WindowList object settings: 'Settings' # Settings object ) -> None: """Store file received from a contact.""" nick = contact_list.get_contact_by_pub_key(onion_pub_key).nick phase("Processing received file", head=1) try: file_pt = auth_and_decrypt(file_ct, file_key) except nacl.exceptions.CryptoError: raise FunctionReturn( f"Error: Decryption key for file from {nick} was invalid.") try: file_dc = decompress(file_pt, settings.max_decompress_size) except zlib.error: raise FunctionReturn(f"Error: Failed to decompress file from {nick}.") phase(DONE) print_on_previous_line(reps=2) try: file_name = bytes_to_str(file_dc[:PADDED_UTF32_STR_LENGTH]) except UnicodeError: raise FunctionReturn( f"Error: Name of file from {nick} had invalid encoding.") if not file_name.isprintable() or not file_name or '/' in file_name: raise FunctionReturn(f"Error: Name of file from {nick} was invalid.") f_data = file_dc[PADDED_UTF32_STR_LENGTH:] file_dir = f'{DIR_RECV_FILES}{nick}/' final_name = store_unique(f_data, file_dir, file_name) message = f"Stored file from {nick} as '{final_name}'." if settings.traffic_masking and window_list.active_win is not None: window = window_list.active_win else: window = window_list.get_window(onion_pub_key) window.add_new(ts, message, onion_pub_key, output=True, event_msg=True)
def assemble_message_packet(self) -> bytes: """Assemble message packet.""" padded = b''.join([p[1:] for p in self.assembly_pt_list]) payload = rm_padding_bytes(padded) if len(self.assembly_pt_list) > 1: msg_ct = payload[:-KEY_LENGTH] msg_key = payload[-KEY_LENGTH:] try: payload = auth_and_decrypt(msg_ct, msg_key, soft_e=True) except (nacl.exceptions.CryptoError, nacl.exceptions.ValueError): raise FunctionReturn("Error: Decryption of message failed.") try: return zlib.decompress(payload) except zlib.error: raise FunctionReturn("Error: Decompression of message failed.")
def _load_contacts(self) -> None: """Load contacts from the encrypted database. This function first reads and decrypts the database content. It then splits the plaintext into a list of 1124-byte blocks: each block contains the serialized data of one contact. Next, the function will remove from the list all dummy contacts (that start with dummy contact's public key). The function will then populate the `self.contacts` list with Contact objects, the data of which is sliced and decoded from the dummy-free blocks. """ with open(self.file_name, 'rb') as f: ct_bytes = f.read() pt_bytes = auth_and_decrypt(ct_bytes, self.master_key.master_key, database=self.file_name) blocks = split_byte_string(pt_bytes, item_len=CONTACT_LENGTH) df_blocks = [ b for b in blocks if not b.startswith(self.dummy_contact.onion_pub_key) ] for block in df_blocks: if len(block) != CONTACT_LENGTH: raise CriticalError("Invalid data in contact database.") (onion_pub_key, tx_fingerprint, rx_fingerprint, kex_status_byte, log_messages_byte, file_reception_byte, notifications_byte, nick_bytes) = separate_headers( block, [ONION_SERVICE_PUBLIC_KEY_LENGTH] + 2 * [FINGERPRINT_LENGTH] + [KEX_STATUS_LENGTH] + 3 * [ENCODED_BOOLEAN_LENGTH]) self.contacts.append( Contact(onion_pub_key=onion_pub_key, tx_fingerprint=tx_fingerprint, rx_fingerprint=rx_fingerprint, kex_status=kex_status_byte, log_messages=bytes_to_bool(log_messages_byte), file_reception=bytes_to_bool(file_reception_byte), notifications=bytes_to_bool(notifications_byte), nick=bytes_to_str(nick_bytes)))
def load_contacts(self) -> None: """Load contacts from encrypted database.""" with open(self.file_name, 'rb') as f: ct_bytes = f.read() pt_bytes = auth_and_decrypt(ct_bytes, self.master_key.master_key) entries = split_byte_string(pt_bytes, item_len=CONTACT_LENGTH) contacts = [e for e in entries if not e.startswith(self.dummy_id)] for c in contacts: assert len(c) == CONTACT_LENGTH self.contacts.append(Contact(rx_account =bytes_to_str( c[ 0:1024]), tx_account =bytes_to_str( c[1024:2048]), nick =bytes_to_str( c[2048:3072]), tx_fingerprint= c[3072:3104], rx_fingerprint= c[3104:3136], log_messages =bytes_to_bool(c[3136:3137]), file_reception=bytes_to_bool(c[3137:3138]), notifications =bytes_to_bool(c[3138:3139])))
def process_imported_file(ts: 'datetime', packet: bytes, window_list: 'WindowList'): """Decrypt and store imported file.""" while True: try: print('') key = get_b58_key('imported_file') phase("Decrypting file", head=1) file_pt = auth_and_decrypt(packet[1:], key, soft_e=True) phase("Done") break except nacl.exceptions.CryptoError: c_print("Invalid decryption key. Try again.", head=2) print_on_previous_line(reps=6, delay=1.5) except KeyboardInterrupt: raise FunctionReturn("File import aborted.") try: phase("Decompressing file") file_dc = zlib.decompress(file_pt) phase("Done") except zlib.error: raise FunctionReturn("Decompression of file data failed.") try: f_name = bytes_to_str(file_dc[:1024]) except UnicodeError: raise FunctionReturn("Received file had an invalid name.") if not f_name.isprintable(): raise FunctionReturn("Received file had an invalid name.") f_data = file_dc[1024:] final_name = store_unique(f_data, DIR_IMPORTED, f_name) message = "Stored imported file to {}/{}".format(DIR_IMPORTED, final_name) box_print(message, head=1) local_win = window_list.get_local_window() local_win.print_new(ts, message, print_=False)
def process_received_file(payload: bytes, nick: str) -> None: """Process received file assembly packets""" try: f_name, _, _, f_data = payload.split(US_BYTE) except ValueError: raise FunctionReturn("Received file had invalid structure.") try: f_name_d = f_name.decode() except UnicodeError: raise FunctionReturn("Received file had an invalid name.") if not f_name_d.isprintable(): raise FunctionReturn("Received file had an invalid name.") try: f_data = base64.b85decode(f_data) except (binascii.Error, ValueError): raise FunctionReturn("Received file had invalid encoding.") file_ct = f_data[:-32] file_key = f_data[-32:] if len(file_key) != 32: raise FunctionReturn("Received file had an invalid key.") try: file_pt = auth_and_decrypt(file_ct, file_key, soft_e=True) except nacl.exceptions.CryptoError: raise FunctionReturn("Decryption of file data failed.") try: file_dc = zlib.decompress(file_pt) except zlib.error: raise FunctionReturn("Decompression of file data failed.") if len(file_dc) == 0: raise FunctionReturn("Received file did not contain data.") f_dir = f'{DIR_RX_FILES}/{nick}' final_name = store_unique(file_dc, f_dir, f_name_d) box_print(["Stored file from {} as {}.".format(nick, final_name)])