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 a BLAKE2b-based checksum before the database is replaced. The function then reads the up-to-date database content. """ 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 failed integrity check, 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() database_data, digest = separate_trailer(database_data, BLAKE2_DIGEST_LENGTH) if blake2b(database_data) != digest: raise CriticalError( f"Invalid data in login database {self.database_name}") return database_data
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 SoftError("Error: Received file had an invalid structure.") try: file_name = file_name_b.decode() except UnicodeError: raise SoftError("Error: Received file name had an invalid encoding.") if not file_name.isprintable() or not file_name or '/' in file_name: raise SoftError("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 SoftError("Error: Received file had an invalid key.") decrypt_and_store_file(ts, file_ct, file_key, file_name, onion_pub_key, nick, window_list, settings)
def detect_errors(self, packet: bytes) -> bytes: """Handle received packet error detection and/or correction.""" if self.settings.qubes: try: packet = base64.b85decode(packet) except ValueError: raise SoftError( "Error: Received packet had invalid Base85 encoding.") if self.settings.session_serial_error_correction and not self.settings.qubes: try: packet, _ = self.rs.decode(packet) return bytes(packet) except ReedSolomonError: raise SoftError( "Error: Reed-Solomon failed to correct errors in the received packet.", bold=True) else: packet, checksum = separate_trailer(packet, PACKET_CHECKSUM_LENGTH) if hashlib.blake2b( packet, digest_size=PACKET_CHECKSUM_LENGTH).digest() != checksum: raise SoftError( "Warning! Received packet had an invalid checksum.", bold=True) return packet
def verify_file(database_name: str) -> bool: """Verify integrity of file content.""" with open(database_name, 'rb') as f: file_data = f.read() purp_data, digest = separate_trailer(file_data, BLAKE2_DIGEST_LENGTH) return blake2b(purp_data) == digest
def assemble_command_packet(self) -> bytes: """Assemble command 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: payload, cmd_hash = separate_trailer(payload, BLAKE2_DIGEST_LENGTH) if blake2b(payload) != cmd_hash: raise FunctionReturn("Error: Received an invalid command.") try: return decompress(payload, self.settings.max_decompress_size) except zlib.error: raise FunctionReturn("Error: Decompression of command failed.")
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 test_separate_header(self): self.assertEqual( separate_trailer(b"cypherpunk", trailer_length=len(b"punk")), (b"cypher", b"punk"))