From ce405b9252718fe0a2f8b593c9005f6047755b26 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 20 May 2021 15:31:38 +0200 Subject: [PATCH] Migrated all asymmetric crypto operations to ECIES on Curve25519. --- Examples/Broadcast.py | 2 +- Examples/Echo.py | 6 +- Examples/Filetransfer.py | 18 +-- Examples/Link.py | 12 +- RNS/Destination.py | 10 +- RNS/Identity.py | 293 ++++++++++++++++++++++----------------- RNS/Link.py | 27 ++-- RNS/Packet.py | 37 +++-- RNS/Resource.py | 7 +- RNS/Transport.py | 4 +- 10 files changed, 231 insertions(+), 185 deletions(-) diff --git a/Examples/Broadcast.py b/Examples/Broadcast.py index 9b18970..76acc70 100644 --- a/Examples/Broadcast.py +++ b/Examples/Broadcast.py @@ -39,7 +39,7 @@ def program_setup(configpath, channel=None): # We specify a callback that will get called every time # the destination receives data. - broadcast_destination.packet_callback(packet_callback) + broadcast_destination.set_packet_callback(packet_callback) # Everything's ready! # Let's hand over control to the main loop diff --git a/Examples/Echo.py b/Examples/Echo.py index 6725e66..2658def 100644 --- a/Examples/Echo.py +++ b/Examples/Echo.py @@ -52,7 +52,7 @@ def server(configpath): # Tell the destination which function in our program to # run when a packet is received. We do this so we can # print a log message when the server receives a request - echo_destination.packet_callback(server_callback) + echo_destination.set_packet_callback(server_callback) # Everything's ready! # Let's Wait for client requests or user input @@ -175,7 +175,7 @@ def client(destination_hexhash, configpath, timeout=None): # We can then set a delivery callback on the receipt. # This will get automatically called when a proof for # this specific packet is received from the destination. - packet_receipt.delivery_callback(packet_delivered) + packet_receipt.set_delivery_callback(packet_delivered) # Tell the user that the echo request was sent RNS.log("Sent echo request to "+RNS.prettyhexrep(request_destination.hash)) @@ -189,7 +189,7 @@ def client(destination_hexhash, configpath, timeout=None): # receives a proof packet. def packet_delivered(receipt): if receipt.status == RNS.PacketReceipt.DELIVERED: - rtt = receipt.rtt() + rtt = receipt.get_rtt() if (rtt >= 1): rtt = round(rtt, 3) rttstring = str(rtt)+" seconds" diff --git a/Examples/Filetransfer.py b/Examples/Filetransfer.py index 7142bfa..f1d89b9 100644 --- a/Examples/Filetransfer.py +++ b/Examples/Filetransfer.py @@ -65,7 +65,7 @@ def server(configpath, path): # We configure a function that will get called every time # a new client creates a link to this destination. - server_destination.link_established_callback(client_connected) + server_destination.set_link_established_callback(client_connected) # Everything's ready! # Let's Wait for client requests or user input @@ -102,7 +102,7 @@ def client_connected(link): if os.path.isdir(serve_path): RNS.log("Client connected, sending file list...") - link.link_closed_callback(client_disconnected) + link.set_link_closed_callback(client_disconnected) # We pack a list of files for sending in a packet data = umsgpack.packb(list_files()) @@ -114,7 +114,7 @@ def client_connected(link): list_packet = RNS.Packet(link, data) list_receipt = list_packet.send() list_receipt.set_timeout(APP_TIMEOUT) - list_receipt.delivery_callback(list_delivered) + list_receipt.set_delivery_callback(list_delivered) list_receipt.timeout_callback(list_timeout) else: RNS.log("Too many files in served directory!", RNS.LOG_ERROR) @@ -125,7 +125,7 @@ def client_connected(link): # open until the client requests a file. We'll # configure a function that get's called when # the client sends a packet with a file request. - link.packet_callback(client_request) + link.set_packet_callback(client_request) else: RNS.log("Client connected, but served path no longer exists!", RNS.LOG_ERROR) link.teardown() @@ -254,18 +254,18 @@ def client(destination_hexhash, configpath): # We expect any normal data packets on the link # to contain a list of served files, so we set # a callback accordingly - link.packet_callback(filelist_received) + link.set_packet_callback(filelist_received) # We'll also set up functions to inform the # user when the link is established or closed - link.link_established_callback(link_established) - link.link_closed_callback(link_closed) + link.set_link_established_callback(link_established) + link.set_link_closed_callback(link_closed) # And set the link to automatically begin # downloading advertised resources link.set_resource_strategy(RNS.Link.ACCEPT_ALL) - link.resource_started_callback(download_began) - link.resource_concluded_callback(download_concluded) + link.set_resource_started_callback(download_began) + link.set_resource_concluded_callback(download_concluded) menu() diff --git a/Examples/Link.py b/Examples/Link.py index 1b9ec2f..71df9a0 100644 --- a/Examples/Link.py +++ b/Examples/Link.py @@ -44,7 +44,7 @@ def server(configpath): # We configure a function that will get called every time # a new client creates a link to this destination. - server_destination.link_established_callback(client_connected) + server_destination.set_link_established_callback(client_connected) # Everything's ready! # Let's Wait for client requests or user input @@ -76,8 +76,8 @@ def client_connected(link): global latest_client_link RNS.log("Client connected") - link.link_closed_callback(client_disconnected) - link.packet_callback(server_packet_received) + link.set_link_closed_callback(client_disconnected) + link.set_packet_callback(server_packet_received) latest_client_link = link def client_disconnected(link): @@ -149,12 +149,12 @@ def client(destination_hexhash, configpath): # We set a callback that will get executed # every time a packet is received over the # link - link.packet_callback(client_packet_received) + link.set_packet_callback(client_packet_received) # We'll also set up functions to inform the # user when the link is established or closed - link.link_established_callback(link_established) - link.link_closed_callback(link_closed) + link.set_link_established_callback(link_established) + link.set_link_closed_callback(link_closed) # Everything is set up, so let's enter a loop # for the user to interact with the example diff --git a/RNS/Destination.py b/RNS/Destination.py index 49e67de..f788e48 100755 --- a/RNS/Destination.py +++ b/RNS/Destination.py @@ -133,8 +133,8 @@ class Destination: def announce(self, app_data=None, path_response=False): """ - Creates an announce packet for this destination and broadcasts it on - all interfaces. Application specific data can be added to the announce. + Creates an announce packet for this destination and broadcasts it on all + relevant interfaces. Application specific data can be added to the announce. :param app_data: *bytes* containing the app_data. :param path_response: Internal flag used by :ref:`RNS.Transport`. Ignore. @@ -172,7 +172,7 @@ class Destination: RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context).send() - def link_established_callback(self, callback): + def set_link_established_callback(self, callback): """ Registers a function to be called when a link has been established to this destination. @@ -181,7 +181,7 @@ class Destination: """ self.callbacks.link_established = callback - def packet_callback(self, callback): + def set_packet_callback(self, callback): """ Registers a function to be called when a packet has been received by this destination. @@ -190,7 +190,7 @@ class Destination: """ self.callbacks.packet = callback - def proof_requested_callback(self, callback): + def set_proof_requested_callback(self, callback): """ Registers a function to be called when a proof has been requested for a packet sent to this destination. Allows control over when and if diff --git a/RNS/Identity.py b/RNS/Identity.py index cda5b88..489105d 100644 --- a/RNS/Identity.py +++ b/RNS/Identity.py @@ -4,14 +4,15 @@ import os import RNS import time import atexit +import base64 from .vendor import umsgpack as umsgpack -from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.serialization import load_der_public_key -from cryptography.hazmat.primitives.serialization import load_der_private_key -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey +from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey +from cryptography.hazmat.primitives.kdf.hkdf import HKDF +from cryptography.fernet import Fernet class Identity: """ @@ -19,26 +20,29 @@ class Identity: for encryption, decryption, signatures and verification, and is the basis for all encrypted communication over Reticulum networks. - :param public_only: Specifies whether this destination only holds a public key. + :param create_keys: Specifies whether new encryption and signing keys should be generated. """ - KEYSIZE = 1024 + + CURVE = "Curve25519" """ - RSA key size in bits. + The curve used for Elliptic Curve DH key exchanges """ - DERKEYSIZE = KEYSIZE+272 + + KEYSIZE = 256*2 + """ + X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key. + """ # Non-configurable constants - PADDINGSIZE = 336 # In bits - HASHLENGTH = 256 # In bits - SIGLENGTH = KEYSIZE - - ENCRYPT_CHUNKSIZE = (KEYSIZE-PADDINGSIZE)//8 - DECRYPT_CHUNKSIZE = KEYSIZE//8 + AES_HMAC_OVERHEAD = 58 # In bytes + AES128_BLOCKSIZE = 16 # In bytes + HASHLENGTH = 256 # In bits + SIGLENGTH = KEYSIZE # In bits TRUNCATED_HASHLENGTH = 80 # In bits """ Constant specifying the truncated hash length (in bits) used by Reticulum - for addressable hashes. Non-configurable. + for addressable hashes and other purposes. Non-configurable. """ # Storage @@ -60,7 +64,7 @@ class Identity: RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME) if destination_hash in Identity.known_destinations: identity_data = Identity.known_destinations[destination_hash] - identity = Identity(public_only=True) + identity = Identity(create_keys=False) identity.load_public_key(identity_data[2]) identity.app_data = identity_data[3] RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME) @@ -145,19 +149,19 @@ class Identity: if packet.packet_type == RNS.Packet.ANNOUNCE: RNS.log("Validating announce from "+RNS.prettyhexrep(packet.destination_hash), RNS.LOG_DEBUG) destination_hash = packet.destination_hash - public_key = packet.data[10:Identity.DERKEYSIZE//8+10] - random_hash = packet.data[Identity.DERKEYSIZE//8+10:Identity.DERKEYSIZE//8+20] - signature = packet.data[Identity.DERKEYSIZE//8+20:Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8] + public_key = packet.data[10:Identity.KEYSIZE//8+10] + random_hash = packet.data[Identity.KEYSIZE//8+10:Identity.KEYSIZE//8+20] + signature = packet.data[Identity.KEYSIZE//8+20:Identity.KEYSIZE//8+20+Identity.KEYSIZE//8] app_data = b"" - if len(packet.data) > Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8: - app_data = packet.data[Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8:] + if len(packet.data) > Identity.KEYSIZE//8+20+Identity.KEYSIZE//8: + app_data = packet.data[Identity.KEYSIZE//8+20+Identity.KEYSIZE//8:] signed_data = destination_hash+public_key+random_hash+app_data - if not len(packet.data) > Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8: + if not len(packet.data) > Identity.KEYSIZE//8+20+Identity.KEYSIZE//8: app_data = None - announced_identity = Identity(public_only=True) + announced_identity = Identity(create_keys=False) announced_identity.load_public_key(public_key) if announced_identity.pub != None and announced_identity.validate(signature, signed_data): @@ -184,40 +188,71 @@ class Identity: :param path: The full path to the saved :ref:`RNS.Identity` data :returns: A :ref:`RNS.Identity` instance, or *None* if the loaded data was invalid. """ - identity = Identity(public_only=True) + identity = Identity(create_keys=False) if identity.load(path): return identity else: return None + @staticmethod + def from_bytes(prv_bytes): + """ + Create a new :ref:`RNS.Identity` instance from *bytes* of private key. + Can be used to load previously created and saved identities into Reticulum. - def __init__(self,public_only=False): + :param prv_bytes: The *bytes* of private a saved private key. **HAZARD!** Never not use this to generate a new key by feeding random data in prv_bytes. + :returns: A :ref:`RNS.Identity` instance, or *None* if the *bytes* data was invalid. + """ + identity = Identity(create_keys=False) + if identity.load_private_key(prv_bytes): + return identity + else: + return None + + + def __init__(self,create_keys=True): # Initialize keys to none - self.prv = None - self.pub = None - self.prv_bytes = None - self.pub_bytes = None - self.hash = None - self.hexhash = None + self.prv = None + self.prv_bytes = None + self.sig_prv = None + self.sig_prv_bytes = None - if not public_only: + self.pub = None + self.pub_bytes = None + self.sig_pub = None + self.sig_pub_bytes = None + + self.hash = None + self.hexhash = None + + if create_keys: self.create_keys() def create_keys(self): - self.prv = rsa.generate_private_key( - public_exponent=65537, - key_size=Identity.KEYSIZE, - backend=default_backend() - ) - self.prv_bytes = self.prv.private_bytes( - encoding=serialization.Encoding.DER, - format=serialization.PrivateFormat.PKCS8, + self.prv = X25519PrivateKey.generate() + self.prv_bytes = self.prv.private_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption() ) - self.pub = self.prv.public_key() - self.pub_bytes = self.pub.public_bytes( - encoding=serialization.Encoding.DER, - format=serialization.PublicFormat.SubjectPublicKeyInfo + + self.sig_prv = Ed25519PrivateKey.generate() + self.sig_prv_bytes = self.sig_prv.private_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PrivateFormat.Raw, + encryption_algorithm=serialization.NoEncryption() + ) + + self.pub = self.prv.public_key() + self.pub_bytes = self.pub.public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw + ) + + self.sig_pub = self.sig_prv.public_key() + self.sig_pub_bytes = self.sig_pub.public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw ) self.update_hashes() @@ -228,13 +263,13 @@ class Identity: """ :returns: The private key as *bytes* """ - return self.prv_bytes + return self.prv_bytes+self.sig_prv_bytes def get_public_key(self): """ :returns: The public key as *bytes* """ - return self.pub_bytes + return self.pub_bytes+self.sig_pub_bytes def load_private_key(self, prv_bytes): """ @@ -244,42 +279,53 @@ class Identity: :returns: True if the key was loaded, otherwise False. """ try: - self.prv_bytes = prv_bytes - self.prv = serialization.load_der_private_key( - self.prv_bytes, - password=None, - backend=default_backend() + self.prv_bytes = prv_bytes[:Identity.KEYSIZE//8//2] + self.prv = X25519PrivateKey.from_private_bytes(self.prv_bytes) + self.sig_prv_bytes = prv_bytes[Identity.KEYSIZE//8//2:] + self.sig_prv = Ed25519PrivateKey.from_private_bytes(self.sig_prv_bytes) + + self.pub = self.prv.public_key() + self.pub_bytes = self.pub.public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw ) - self.pub = self.prv.public_key() - self.pub_bytes = self.pub.public_bytes( - encoding=serialization.Encoding.DER, - format=serialization.PublicFormat.SubjectPublicKeyInfo + + self.sig_pub = self.sig_prv.public_key() + self.sig_pub_bytes = self.sig_pub.public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw ) + self.update_hashes() return True except Exception as e: + raise e RNS.log("Failed to load identity key", RNS.LOG_ERROR) RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) return False - def load_public_key(self, key): + def load_public_key(self, pub_bytes): """ Load a public key into the instance. - :param prv_bytes: The public key as *bytes*. + :param pub_bytes: The public key as *bytes*. :returns: True if the key was loaded, otherwise False. """ try: - self.pub_bytes = key - self.pub = load_der_public_key(self.pub_bytes, backend=default_backend()) + self.pub_bytes = pub_bytes[:Identity.KEYSIZE//8//2] + self.sig_pub_bytes = pub_bytes[Identity.KEYSIZE//8//2:] + + self.pub = X25519PublicKey.from_public_bytes(self.pub_bytes) + self.sig_pub = Ed25519PublicKey.from_public_bytes(self.sig_pub_bytes) + self.update_hashes() except Exception as e: RNS.log("Error while loading public key, the contained exception was: "+str(e), RNS.LOG_ERROR) def update_hashes(self): - self.hash = Identity.truncated_hash(self.pub_bytes) + self.hash = Identity.truncated_hash(self.get_public_key()) self.hexhash = self.hash.hex() def to_file(self, path): @@ -310,71 +356,78 @@ class Identity: RNS.log("Error while loading identity from "+str(path), RNS.LOG_ERROR) RNS.log("The contained exception was: "+str(e)) + def get_salt(self): + return self.hash + + def get_context(self): + return None + def encrypt(self, plaintext): """ Encrypts information for the identity. :param plaintext: The plaintext to be encrypted as *bytes*. - :returns: Ciphertext as *bytes*. - :raises: *KeyError* if the instance does not hold a public key + :returns: Ciphertext token as *bytes*. + :raises: *KeyError* if the instance does not hold a public key. """ if self.pub != None: - chunksize = Identity.ENCRYPT_CHUNKSIZE - chunks = int(math.ceil(len(plaintext)/(float(chunksize)))) + ephemeral_key = X25519PrivateKey.generate() + ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw + ) - ciphertext = b""; - for chunk in range(chunks): - start = chunk*chunksize - end = (chunk+1)*chunksize - if (chunk+1)*chunksize > len(plaintext): - end = len(plaintext) - - ciphertext += self.pub.encrypt( - plaintext[start:end], - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), - label=None - ) - ) - return ciphertext + shared_key = ephemeral_key.exchange(self.pub) + derived_key = derived_key = HKDF( + algorithm=hashes.SHA256(), + length=32, + salt=self.get_salt(), + info=self.get_context(), + ).derive(shared_key) + + fernet = Fernet(base64.urlsafe_b64encode(derived_key)) + ciphertext = base64.urlsafe_b64decode(fernet.encrypt(plaintext)) + token = ephemeral_pub_bytes+ciphertext + + return token else: raise KeyError("Encryption failed because identity does not hold a public key") - def decrypt(self, ciphertext): + def decrypt(self, ciphertext_token): """ Decrypts information for the identity. :param ciphertext: The ciphertext to be decrypted as *bytes*. :returns: Plaintext as *bytes*, or *None* if decryption fails. - :raises: *KeyError* if the instance does not hold a private key + :raises: *KeyError* if the instance does not hold a private key. """ if self.prv != None: - plaintext = None - try: - chunksize = Identity.DECRYPT_CHUNKSIZE - chunks = int(math.ceil(len(ciphertext)/(float(chunksize)))) + if len(ciphertext_token) > Identity.KEYSIZE//8//2: + plaintext = None + try: + peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2] + peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes) - plaintext = b""; - for chunk in range(chunks): - start = chunk*chunksize - end = (chunk+1)*chunksize - if (chunk+1)*chunksize > len(ciphertext): - end = len(ciphertext) + shared_key = self.prv.exchange(peer_pub) + derived_key = derived_key = HKDF( + algorithm=hashes.SHA256(), + length=32, + salt=self.get_salt(), + info=self.get_context(), + ).derive(shared_key) - plaintext += self.prv.decrypt( - ciphertext[start:end], - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), - label=None - ) - ) - except: - RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed", RNS.LOG_VERBOSE) - - return plaintext; + fernet = Fernet(base64.urlsafe_b64encode(derived_key)) + ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:] + plaintext = fernet.decrypt(base64.urlsafe_b64encode(ciphertext)) + + except Exception as e: + RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed: "+str(e), RNS.LOG_DEBUG) + + return plaintext; + else: + RNS.log("Decryption failed because the token size was invalid.", RNS.LOG_DEBUG) + return None else: raise KeyError("Decryption failed because identity does not hold a private key") @@ -385,18 +438,14 @@ class Identity: :param message: The message to be signed as *bytes*. :returns: Signature as *bytes*. - :raises: *KeyError* if the instance does not hold a private key + :raises: *KeyError* if the instance does not hold a private key. """ - if self.prv != None: - signature = self.prv.sign( - message, - padding.PSS( - mgf=padding.MGF1(hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA256() - ) - return signature + if self.sig_prv != None: + try: + return self.sig_prv.sign(message) + except Exception as e: + RNS.log("The identity "+str(self)+" could not sign the requested message. The contained exception was: "+str(e), RNS.LOG_ERROR) + raise e else: raise KeyError("Signing failed because identity does not hold a private key") @@ -407,19 +456,11 @@ class Identity: :param signature: The signature to be validated as *bytes*. :param message: The message to be validated as *bytes*. :returns: True if the signature is valid, otherwise False. - :raises: *KeyError* if the instance does not hold a public key + :raises: *KeyError* if the instance does not hold a public key. """ if self.pub != None: try: - self.pub.verify( - signature, - message, - padding.PSS( - mgf=padding.MGF1(hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA256() - ) + self.sig_pub.verify(signature, message) return True except Exception as e: return False diff --git a/RNS/Link.py b/RNS/Link.py index e7cc28c..eacec7e 100644 --- a/RNS/Link.py +++ b/RNS/Link.py @@ -33,17 +33,15 @@ class Link: :param peer_pub_bytes: Internal use, ignore this argument. :param peer_sig_pub_bytes: Internal use, ignore this argument. """ - CURVE = "Curve25519" + CURVE = RNS.Identity.CURVE """ The curve used for Elliptic Curve DH key exchanges """ - ECPUBSIZE = 32+32 - BLOCKSIZE = 16 - KEYSIZE = 32 + ECPUBSIZE = 32+32 + KEYSIZE = 32 - AES_HMAC_OVERHEAD = 58 - MDU = math.floor((RNS.Reticulum.MDU-AES_HMAC_OVERHEAD)/BLOCKSIZE)*BLOCKSIZE - 1 + MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.AES_HMAC_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1 # TODO: This should not be hardcoded, # but calculated from something like @@ -89,11 +87,6 @@ class Link: RNS.Transport.register_link(link) link.last_inbound = time.time() link.start_watchdog() - - # TODO: Why was link_established callback here? Seems weird - # to call this before RTT packet has been received - #if self.owner.callbacks.link_established != None: - # self.owner.callbacks.link_established(link) RNS.log("Incoming link request "+str(link)+" accepted", RNS.LOG_VERBOSE) return link @@ -537,13 +530,13 @@ class Link: except Exception as e: return False - def link_established_callback(self, callback): + def set_link_established_callback(self, callback): self.callbacks.link_established = callback - def link_closed_callback(self, callback): + def set_link_closed_callback(self, callback): self.callbacks.link_closed = callback - def packet_callback(self, callback): + def set_packet_callback(self, callback): """ Registers a function to be called when a packet has been received over this link. @@ -552,7 +545,7 @@ class Link: """ self.callbacks.packet = callback - def resource_callback(self, callback): + def set_resource_callback(self, callback): """ Registers a function to be called when a resource has been advertised over this link. If the function returns *True* @@ -563,7 +556,7 @@ class Link: """ self.callbacks.resource = callback - def resource_started_callback(self, callback): + def set_resource_started_callback(self, callback): """ Registers a function to be called when a resource has begun transferring over this link. @@ -572,7 +565,7 @@ class Link: """ self.callbacks.resource_started = callback - def resource_concluded_callback(self, callback): + def set_resource_concluded_callback(self, callback): """ Registers a function to be called when a resource has concluded transferring over this link. diff --git a/RNS/Packet.py b/RNS/Packet.py index 6283cfe..25917e9 100755 --- a/RNS/Packet.py +++ b/RNS/Packet.py @@ -6,8 +6,16 @@ import RNS class Packet: """ - The Packet class is used to create packet instances that can be - sent over a Reticulum network. + The Packet class is used to create packet instances that can be sent + over a Reticulum network. Packets to will automatically be encrypted if + they are adressed to a ``RNS.Destination.SINGLE`` destination, + ``RNS.Destination.GROUP`` destination or a :ref:`RNS.Link`. + + For ``RNS.Destination.GROUP`` destinations, Reticulum will use the + pre-shared key configured for the destination. + + For ``RNS.Destination.SINGLE`` destinations and :ref:`RNS.Link` + destinations, reticulum will use ephemeral keys, and offers **Forward Secrecy**. :param destination: A :ref:`RNS.Destination` instance to which the packet will be sent. :param data: The data payload to be included in the packet as *bytes*. @@ -56,14 +64,21 @@ class Packet: # This is used to calculate allowable # payload sizes - HEADER_MAXSIZE = 23 + HEADER_MAXSIZE = RNS.Reticulum.HEADER_MAXSIZE MDU = RNS.Reticulum.MDU - # With an MTU of 500, the maximum RSA-encrypted - # amount of data we can send in a single packet - # is given by the below calculation; 258 bytes. - RSA_MDU = math.floor(MDU/RNS.Identity.DECRYPT_CHUNKSIZE)*RNS.Identity.ENCRYPT_CHUNKSIZE - PLAIN_MDU = MDU + # TODO: Update this + # With an MTU of 500, the maximum of data we can + # send in a single encrypted packet is given by + # the below calculation; 383 bytes. + ENCRYPTED_MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.AES_HMAC_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1 + """ + The maximum size of the payload data in a single encrypted packet + """ + PLAIN_MDU = MDU + """ + The maximum size of the payload data in a single unencrypted packet + """ # TODO: This should be calculated # more intelligently @@ -406,7 +421,7 @@ class PacketReceipt: else: return False - def rtt(self): + def get_rtt(self): """ :returns: The round-trip-time in seconds """ @@ -439,7 +454,7 @@ class PacketReceipt: """ self.timeout = float(timeout) - def delivery_callback(self, callback): + def set_delivery_callback(self, callback): """ Sets a function that gets called if a successfull delivery has been proven. @@ -449,7 +464,7 @@ class PacketReceipt: # Set a function that gets called if the # delivery times out - def timeout_callback(self, callback): + def set_timeout_callback(self, callback): """ Sets a function that gets called if the delivery times out. diff --git a/RNS/Resource.py b/RNS/Resource.py index 3a44a21..4dc4c22 100644 --- a/RNS/Resource.py +++ b/RNS/Resource.py @@ -17,7 +17,6 @@ class Resource: :param link: The :ref:`RNS.Link` instance on which to transfer the data. :param advertise: Whether to automatically advertise the resource. Can be *True* or *False*. :param auto_compress: Whether to auto-compress the resource. Can be *True* or *False*. - :param auto_compress: Whether the resource must be compressed. Can be *True* or *False*. Used for debugging, will disappear in the future. :param callback: A *callable* with the signature *callback(resource)*. Will be called when the resource transfer concludes. :param progress_callback: A *callable* with the signature *callback(resource)*. Will be called whenever the resource transfer progress is updated. :param segment_index: Internal use, ignore. @@ -134,7 +133,7 @@ class Resource: # Create a resource for transmission to a remote destination # The data passed can be either a bytes-array or a file opened # in binary read mode. - def __init__(self, data, link, advertise=True, auto_compress=True, must_compress=False, callback=None, progress_callback=None, segment_index = 1, original_hash = None): + def __init__(self, data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, segment_index = 1, original_hash = None): data_size = None resource_data = None if hasattr(data, "read"): @@ -198,7 +197,7 @@ class Resource: self.uncompressed_data = data compression_began = time.time() - if must_compress or (auto_compress and len(self.uncompressed_data) < Resource.AUTO_COMPRESS_MAX_SIZE): + if (auto_compress and len(self.uncompressed_data) < Resource.AUTO_COMPRESS_MAX_SIZE): RNS.log("Compressing resource data...", RNS.LOG_DEBUG) self.compressed_data = bz2.compress(self.uncompressed_data) RNS.log("Compression completed in "+str(round(time.time()-compression_began, 3))+" seconds", RNS.LOG_DEBUG) @@ -748,8 +747,6 @@ class Resource: :returns: The current progress of the resource transfer as a *float* between 0.0 and 1.0. """ if self.initiator: - # TODO: Remove - # progress = self.sent_parts / len(self.parts) self.processed_parts = (self.segment_index-1)*math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU) self.processed_parts += self.sent_parts self.progress_total_parts = float(self.grand_total_parts) diff --git a/RNS/Transport.py b/RNS/Transport.py index 8b82372..398d09a 100755 --- a/RNS/Transport.py +++ b/RNS/Transport.py @@ -108,7 +108,7 @@ class Transport: # Create transport-specific destinations Transport.path_request_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request") - Transport.path_request_destination.packet_callback(Transport.path_request_handler) + Transport.path_request_destination.set_packet_callback(Transport.path_request_handler) Transport.control_destinations.append(Transport.path_request_destination) Transport.control_hashes.append(Transport.path_request_destination.hash) @@ -652,7 +652,7 @@ class Transport: # First, check that the announce is not for a destination # local to this system, and that hops are less than the max if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1): - random_blob = packet.data[RNS.Identity.DERKEYSIZE//8+10:RNS.Identity.DERKEYSIZE//8+20] + random_blob = packet.data[RNS.Identity.KEYSIZE//8+10:RNS.Identity.KEYSIZE//8+20] random_blobs = [] if packet.destination_hash in Transport.destination_table: random_blobs = Transport.destination_table[packet.destination_hash][4]