def whisper( user_input: 'UserInput', window: 'TxWindow', settings: 'Settings', queues: 'QueueDict', ) -> None: """\ Send a message to the contact that overrides their enabled logging setting for that message. The functionality of this feature is impossible to enforce, but if the recipient can be trusted and they do not modify their client, this feature can be used to send the message off-the-record. """ try: message = user_input.plaintext.strip().split(' ', 1)[1] except IndexError: raise SoftError("Error: No whisper message specified.", head_clear=True) queue_message(user_input=UserInput(message, MESSAGE), window=window, settings=settings, queues=queues, whisper=True, log_as_ph=True)
def send_file(path: str, settings: 'Settings', queues: 'QueueDict', window: 'TxWindow' ) -> None: """Send file to window members in a single transmission. This is the default mode for file transmission, used when traffic masking is not enabled. The file is loaded and compressed before it is encrypted. The encrypted file is then exported to Networked Computer along with a list of Onion Service public keys (members in window) of all recipients to whom the Relay Program will multi-cast the file to. Once the file ciphertext has been exported, this function will multi-cast the file decryption key to each recipient inside an automated key delivery message that uses a special FILE_KEY_HEADER in place of standard PRIVATE_MESSAGE_HEADER. To know for which file ciphertext the key is for, an identifier must be added to the key delivery message. The identifier in this case is the BLAKE2b digest of the ciphertext itself. The reason of using the digest as the identifier is, it authenticates both the ciphertext and its origin. To understand this, consider the following attack scenario: Let the file ciphertext identifier be just a random 32-byte value "ID". 1) Alice sends Bob and Chuck (a malicious common peer) a file ciphertext and identifier CT|ID (where | denotes concatenation). 2) Chuck who has compromised Bob's Networked Computer interdicts the CT|ID from Alice. 3) Chuck decrypts CT in his end, makes edits to the plaintext PT to create PT'. 4) Chuck re-encrypts PT' with the same symmetric key to produce CT'. 5) Chuck re-uses the ID and produces CT'|ID. 6) Chuck uploads the CT'|ID to Bob's Networked Computer and replaces the interdicted CT|ID with it. 7) When Bob' Receiver Program receives the automated key delivery message from Alice, his Receiver program uses the bundled ID to identify the key is for CT'. 8) Bob's Receiver decrypts CT' using the newly received key and obtains Chuck's PT', that appears to come from Alice. Now, consider a situation where the ID is instead calculated ID = BLAKE2b(CT), if Chuck edits the PT, the CT' will by definition be different from CT, and the BLAKE2b digest will also be different. In order to make Bob decrypt CT', Chuck needs to also change the hash in Alice's key delivery message, which means Chuck needs to create an existential forgery of the TFC message. Since the Poly1305 tag prevents this, the calculated ID is enough to authenticate the ciphertext. If Chuck attempts to send their own key delivery message, Chuck's own Onion Service public key used to identify the TFC message key (decryption key for the key delivery message) will be permanently associated with the file hash, so if they inject a file CT, and Bob has decided to enable file reception for Chuck, the file CT will appear to come from Chuck, and not from Alice. From the perspective of Bob, it's as if Chuck had dropped Alice's file and sent him another file instead. """ from src.transmitter.windows import MockWindow # Avoid circular import if settings.traffic_masking: raise FunctionReturn("Error: Command is disabled during traffic masking.", head_clear=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.", head_clear=True) if os.path.getsize(path) == 0: raise FunctionReturn("Error: Target file is empty.", head_clear=True) phase("Reading data") with open(path, 'rb') as f: data.extend(f.read()) phase(DONE) print_on_previous_line(flush=True) phase("Compressing data") comp = bytes(zlib.compress(bytes(data), level=COMPRESSION_LEVEL)) phase(DONE) print_on_previous_line(flush=True) phase("Encrypting data") file_key = csprng() file_ct = encrypt_and_sign(comp, file_key) ct_hash = blake2b(file_ct) phase(DONE) print_on_previous_line(flush=True) phase("Exporting data") no_contacts = int_to_bytes(len(window)) ser_contacts = b''.join([c.onion_pub_key for c in window]) file_packet = FILE_DATAGRAM_HEADER + no_contacts + ser_contacts + file_ct queue_to_nc(file_packet, queues[RELAY_PACKET_QUEUE]) key_delivery_msg = base64.b85encode(ct_hash + file_key).decode() for contact in window: queue_message(user_input=UserInput(key_delivery_msg, MESSAGE), window =MockWindow(contact.onion_pub_key, [contact]), settings =settings, queues =queues, header =FILE_KEY_HEADER, log_as_ph =True) phase(DONE) print_on_previous_line(flush=True) m_print(f"Sent file '{name}' to {window.type_print} {window.name}.")
def test_user_input(self): user_input = UserInput('test_plaintext', FILE) self.assertEqual(user_input.plaintext, 'test_plaintext') self.assertEqual(user_input.type, FILE)