Migrated all asymmetric crypto operations to ECIES on Curve25519.
This commit is contained in:
		
							parent
							
								
									7f5625a526
								
							
						
					
					
						commit
						ce405b9252
					
				| @ -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 | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
| @ -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() | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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<api-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 | ||||
|  | ||||
							
								
								
									
										293
									
								
								RNS/Identity.py
									
									
									
									
									
								
							
							
						
						
									
										293
									
								
								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<api-identity>` data | ||||
|         :returns: A :ref:`RNS.Identity<api-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<api-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<api-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 | ||||
|  | ||||
							
								
								
									
										27
									
								
								RNS/Link.py
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								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. | ||||
|  | ||||
| @ -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<api-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<api-link>` | ||||
|     destinations, reticulum will use ephemeral keys, and offers **Forward Secrecy**. | ||||
| 
 | ||||
|     :param destination: A :ref:`RNS.Destination<api-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. | ||||
| 
 | ||||
|  | ||||
| @ -17,7 +17,6 @@ class Resource: | ||||
|     :param link: The :ref:`RNS.Link<api-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) | ||||
|  | ||||
| @ -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] | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user