Migrated all asymmetric crypto operations to ECIES on Curve25519.

This commit is contained in:
Mark Qvist 2021-05-20 15:31:38 +02:00
parent 7f5625a526
commit ce405b9252
10 changed files with 231 additions and 185 deletions

View File

@ -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

View File

@ -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"

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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]