mirror of
https://github.com/markqvist/Reticulum.git
synced 2024-11-22 13:40:19 +00:00
Expanded and documented ratchet API
This commit is contained in:
parent
320704f812
commit
9ef10a7b3e
@ -72,7 +72,16 @@ class Destination:
|
|||||||
directions = [IN, OUT]
|
directions = [IN, OUT]
|
||||||
|
|
||||||
PR_TAG_WINDOW = 30
|
PR_TAG_WINDOW = 30
|
||||||
RATCHET_COUNT = 512
|
|
||||||
|
RATCHET_COUNT = 512
|
||||||
|
"""
|
||||||
|
The default number of generated ratchet keys a destination will retain, if it has ratchets enabled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
RATCHET_INTERVAL = 30*60
|
||||||
|
"""
|
||||||
|
The minimum interval between rotating ratchet keys, in seconds.
|
||||||
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def expand_name(identity, app_name, *aspects):
|
def expand_name(identity, app_name, *aspects):
|
||||||
@ -142,6 +151,10 @@ class Destination:
|
|||||||
self.proof_strategy = Destination.PROVE_NONE
|
self.proof_strategy = Destination.PROVE_NONE
|
||||||
self.ratchets = None
|
self.ratchets = None
|
||||||
self.ratchets_path = None
|
self.ratchets_path = None
|
||||||
|
self.ratchet_interval = Destination.RATCHET_INTERVAL
|
||||||
|
self.retained_ratchets = Destination.RATCHET_COUNT
|
||||||
|
self.latest_ratchet_time = None
|
||||||
|
self.__enforce_ratchets = False
|
||||||
self.mtu = 0
|
self.mtu = 0
|
||||||
|
|
||||||
self.path_responses = {}
|
self.path_responses = {}
|
||||||
@ -175,36 +188,12 @@ class Destination:
|
|||||||
"""
|
"""
|
||||||
return "<"+self.name+"/"+self.hexhash+">"
|
return "<"+self.name+"/"+self.hexhash+">"
|
||||||
|
|
||||||
def enable_ratchets(self, ratchets_path):
|
def _clean_ratchets(self):
|
||||||
if ratchets_path != None:
|
if self.ratchets != None:
|
||||||
if os.path.isfile(ratchets_path):
|
if len (self.ratchets) > self.retained_ratchets:
|
||||||
try:
|
self.ratchets = self.ratchets[:Destination.RATCHET_COUNT]
|
||||||
ratchets_file = open(ratchets_path, "rb")
|
|
||||||
persisted_data = umsgpack.unpackb(ratchets_file.read())
|
|
||||||
if "signature" in persisted_data and "ratchets" in persisted_data:
|
|
||||||
if self.identity.validate(persisted_data["signature"], persisted_data["ratchets"]):
|
|
||||||
self.ratchets = umsgpack.unpackb(persisted_data["ratchets"])
|
|
||||||
self.ratchets_path = ratchets_path
|
|
||||||
else:
|
|
||||||
raise KeyError("Invalid ratchet file signature")
|
|
||||||
|
|
||||||
except Exception as e:
|
def _persist_ratchets(self):
|
||||||
self.ratchets = None
|
|
||||||
self.ratchets_path = None
|
|
||||||
raise OSError("Could not read ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
else:
|
|
||||||
RNS.log("No existing ratchet data found, initialising new ratchet file for "+str(self), RNS.LOG_DEBUG)
|
|
||||||
self.ratchets = []
|
|
||||||
self.ratchets_path = ratchets_path
|
|
||||||
self.persist_ratchets()
|
|
||||||
|
|
||||||
RNS.log("Ratchets enabled on "+str(self), RNS.LOG_DEBUG) # TODO: Remove
|
|
||||||
return True
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ValueError("No ratchet file path specified for "+str(self))
|
|
||||||
|
|
||||||
def persist_ratchets(self):
|
|
||||||
try:
|
try:
|
||||||
packed_ratchets = umsgpack.packb(self.ratchets)
|
packed_ratchets = umsgpack.packb(self.ratchets)
|
||||||
persisted_data = {"signature": self.sign(packed_ratchets), "ratchets": packed_ratchets}
|
persisted_data = {"signature": self.sign(packed_ratchets), "ratchets": packed_ratchets}
|
||||||
@ -218,15 +207,20 @@ class Destination:
|
|||||||
|
|
||||||
def rotate_ratchets(self):
|
def rotate_ratchets(self):
|
||||||
if self.ratchets != None:
|
if self.ratchets != None:
|
||||||
RNS.log("Rotating ratchets for "+str(self), RNS.LOG_DEBUG) # TODO: Remove
|
now = time.time()
|
||||||
new_ratchet = RNS.Identity._generate_ratchet()
|
if now > self.latest_ratchet_time+self.ratchet_interval:
|
||||||
self.ratchets.insert(0, new_ratchet)
|
RNS.log("Rotating ratchets for "+str(self), RNS.LOG_DEBUG)
|
||||||
if len (self.ratchets) > Destination.RATCHET_COUNT:
|
new_ratchet = RNS.Identity._generate_ratchet()
|
||||||
self.ratchets = self.ratchets[:Destination.RATCHET_COUNT]
|
self.ratchets.insert(0, new_ratchet)
|
||||||
self.persist_ratchets()
|
self.latest_ratchet_time = now
|
||||||
|
self._clean_ratchets()
|
||||||
|
self._persist_ratchets()
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
raise SystemError("Cannot rotate ratchet on "+str(self)+", ratchets are not enabled")
|
raise SystemError("Cannot rotate ratchet on "+str(self)+", ratchets are not enabled")
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def announce(self, app_data=None, path_response=False, attached_interface=None, tag=None, send=True):
|
def announce(self, app_data=None, path_response=False, attached_interface=None, tag=None, send=True):
|
||||||
"""
|
"""
|
||||||
Creates an announce packet for this destination and broadcasts it on all
|
Creates an announce packet for this destination and broadcasts it on all
|
||||||
@ -272,8 +266,8 @@ class Destination:
|
|||||||
self.rotate_ratchets()
|
self.rotate_ratchets()
|
||||||
ratchet = RNS.Identity._ratchet_public_bytes(self.ratchets[0])
|
ratchet = RNS.Identity._ratchet_public_bytes(self.ratchets[0])
|
||||||
|
|
||||||
# TODO: Remove debug output
|
# TODO: Remove at some point
|
||||||
RNS.log(f"Including ratchet {RNS.prettyhexrep(RNS.Identity.truncated_hash(ratchet))} in announce", RNS.LOG_DEBUG)
|
RNS.log(f"Including ratchet {RNS.prettyhexrep(RNS.Identity.truncated_hash(ratchet))} in announce", RNS.LOG_EXTREME)
|
||||||
|
|
||||||
if app_data == None and self.default_app_data != None:
|
if app_data == None and self.default_app_data != None:
|
||||||
if isinstance(self.default_app_data, bytes):
|
if isinstance(self.default_app_data, bytes):
|
||||||
@ -366,7 +360,6 @@ class Destination:
|
|||||||
else:
|
else:
|
||||||
self.proof_strategy = proof_strategy
|
self.proof_strategy = proof_strategy
|
||||||
|
|
||||||
|
|
||||||
def register_request_handler(self, path, response_generator = None, allow = ALLOW_NONE, allowed_list = None):
|
def register_request_handler(self, path, response_generator = None, allow = ALLOW_NONE, allowed_list = None):
|
||||||
"""
|
"""
|
||||||
Registers a request handler.
|
Registers a request handler.
|
||||||
@ -388,7 +381,6 @@ class Destination:
|
|||||||
request_handler = [path, response_generator, allow, allowed_list]
|
request_handler = [path, response_generator, allow, allowed_list]
|
||||||
self.request_handlers[path_hash] = request_handler
|
self.request_handlers[path_hash] = request_handler
|
||||||
|
|
||||||
|
|
||||||
def deregister_request_handler(self, path):
|
def deregister_request_handler(self, path):
|
||||||
"""
|
"""
|
||||||
Deregisters a request handler.
|
Deregisters a request handler.
|
||||||
@ -403,8 +395,6 @@ class Destination:
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def receive(self, packet):
|
def receive(self, packet):
|
||||||
if packet.packet_type == RNS.Packet.LINKREQUEST:
|
if packet.packet_type == RNS.Packet.LINKREQUEST:
|
||||||
plaintext = packet.data
|
plaintext = packet.data
|
||||||
@ -419,13 +409,99 @@ class Destination:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while executing receive callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while executing receive callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
|
||||||
def incoming_link_request(self, data, packet):
|
def incoming_link_request(self, data, packet):
|
||||||
if self.accept_link_requests:
|
if self.accept_link_requests:
|
||||||
link = RNS.Link.validate_request(self, data, packet)
|
link = RNS.Link.validate_request(self, data, packet)
|
||||||
if link != None:
|
if link != None:
|
||||||
self.links.append(link)
|
self.links.append(link)
|
||||||
|
|
||||||
|
def enable_ratchets(self, ratchets_path):
|
||||||
|
"""
|
||||||
|
Enables ratchets on the destination. When ratchets are enabled, Reticulum will automatically rotate
|
||||||
|
the keys used to encrypt packets to this destination, and include the latest ratchet key in announces.
|
||||||
|
|
||||||
|
Enabling ratchets on a destination will provide forward secrecy for packets sent to that destination,
|
||||||
|
even when sent outside a ``Link``. The normal Reticulum ``Link`` establishment procedure already performs
|
||||||
|
its own ephemeral key exchange for each link establishment, which means that ratchets are not necessary
|
||||||
|
to provide forward secrecy for links.
|
||||||
|
|
||||||
|
Enabling ratchets will have a small impact on announce size, adding 32 bytes to every sent announce.
|
||||||
|
|
||||||
|
:param ratchets_path: The path to a file to store ratchet data in.
|
||||||
|
:returns: True if the operation succeeded, otherwise False.
|
||||||
|
"""
|
||||||
|
if ratchets_path != None:
|
||||||
|
self.latest_ratchet_time = 0
|
||||||
|
if os.path.isfile(ratchets_path):
|
||||||
|
try:
|
||||||
|
ratchets_file = open(ratchets_path, "rb")
|
||||||
|
persisted_data = umsgpack.unpackb(ratchets_file.read())
|
||||||
|
if "signature" in persisted_data and "ratchets" in persisted_data:
|
||||||
|
if self.identity.validate(persisted_data["signature"], persisted_data["ratchets"]):
|
||||||
|
self.ratchets = umsgpack.unpackb(persisted_data["ratchets"])
|
||||||
|
self.ratchets_path = ratchets_path
|
||||||
|
else:
|
||||||
|
raise KeyError("Invalid ratchet file signature")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.ratchets = None
|
||||||
|
self.ratchets_path = None
|
||||||
|
raise OSError("Could not read ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
else:
|
||||||
|
RNS.log("No existing ratchet data found, initialising new ratchet file for "+str(self), RNS.LOG_DEBUG)
|
||||||
|
self.ratchets = []
|
||||||
|
self.ratchets_path = ratchets_path
|
||||||
|
self._persist_ratchets()
|
||||||
|
|
||||||
|
# TODO: Remove at some point
|
||||||
|
RNS.log("Ratchets enabled on "+str(self), RNS.LOG_DEBUG)
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError("No ratchet file path specified for "+str(self))
|
||||||
|
|
||||||
|
def enforce_ratchets(self):
|
||||||
|
"""
|
||||||
|
When ratchet enforcement is enabled, this destination will never accept packets that use its
|
||||||
|
base Identity key for encryption, but only accept packets encrypted with one of the retained
|
||||||
|
ratchet keys.
|
||||||
|
"""
|
||||||
|
if self.ratchets != None:
|
||||||
|
self.__enforce_ratchets = True
|
||||||
|
RNS.log("Ratchets enforced on "+str(self), RNS.LOG_DEBUG)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def set_retained_ratchets(self, retained_ratchets):
|
||||||
|
"""
|
||||||
|
Sets the number of previously generated ratchet keys this destination will retain,
|
||||||
|
and try to use when decrypting incoming packets. Defaults to ``Destination.RATCHET_COUNT``.
|
||||||
|
|
||||||
|
:param retained_ratchets: The number of generated ratchets to retain.
|
||||||
|
:returns: True if the operation succeeded, False if not.
|
||||||
|
"""
|
||||||
|
if isinstance(retained_ratchets, int) and retained_ratchets > 0:
|
||||||
|
self.retained_ratchets = retained_ratchets
|
||||||
|
self._clean_ratchets()
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def set_ratchet_interval(self, interval):
|
||||||
|
"""
|
||||||
|
Sets the minimum interval in seconds between ratchet key rotation.
|
||||||
|
Defaults to ``Destination.RATCHET_INTERVAL``.
|
||||||
|
|
||||||
|
:param interval: The minimum interval in seconds.
|
||||||
|
:returns: True if the operation succeeded, False if not.
|
||||||
|
"""
|
||||||
|
if isinstance(interval, int) and interval > 0:
|
||||||
|
self.ratchet_interval = interval
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def create_keys(self):
|
def create_keys(self):
|
||||||
"""
|
"""
|
||||||
For a ``RNS.Destination.GROUP`` type destination, creates a new symmetric key.
|
For a ``RNS.Destination.GROUP`` type destination, creates a new symmetric key.
|
||||||
@ -442,7 +518,6 @@ class Destination:
|
|||||||
self.prv_bytes = Fernet.generate_key()
|
self.prv_bytes = Fernet.generate_key()
|
||||||
self.prv = Fernet(self.prv_bytes)
|
self.prv = Fernet(self.prv_bytes)
|
||||||
|
|
||||||
|
|
||||||
def get_private_key(self):
|
def get_private_key(self):
|
||||||
"""
|
"""
|
||||||
For a ``RNS.Destination.GROUP`` type destination, returns the symmetric private key.
|
For a ``RNS.Destination.GROUP`` type destination, returns the symmetric private key.
|
||||||
@ -456,7 +531,6 @@ class Destination:
|
|||||||
else:
|
else:
|
||||||
return self.prv_bytes
|
return self.prv_bytes
|
||||||
|
|
||||||
|
|
||||||
def load_private_key(self, key):
|
def load_private_key(self, key):
|
||||||
"""
|
"""
|
||||||
For a ``RNS.Destination.GROUP`` type destination, loads a symmetric private key.
|
For a ``RNS.Destination.GROUP`` type destination, loads a symmetric private key.
|
||||||
@ -480,7 +554,6 @@ class Destination:
|
|||||||
else:
|
else:
|
||||||
raise TypeError("A single destination holds keys through an Identity instance")
|
raise TypeError("A single destination holds keys through an Identity instance")
|
||||||
|
|
||||||
|
|
||||||
def encrypt(self, plaintext):
|
def encrypt(self, plaintext):
|
||||||
"""
|
"""
|
||||||
Encrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
|
Encrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
|
||||||
@ -504,8 +577,6 @@ class Destination:
|
|||||||
else:
|
else:
|
||||||
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def decrypt(self, ciphertext):
|
def decrypt(self, ciphertext):
|
||||||
"""
|
"""
|
||||||
Decrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
|
Decrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
|
||||||
@ -517,7 +588,7 @@ class Destination:
|
|||||||
return ciphertext
|
return ciphertext
|
||||||
|
|
||||||
if self.type == Destination.SINGLE and self.identity != None:
|
if self.type == Destination.SINGLE and self.identity != None:
|
||||||
return self.identity.decrypt(ciphertext, ratchets=self.ratchets)
|
return self.identity.decrypt(ciphertext, ratchets=self.ratchets, enforce_ratchets=self.__enforce_ratchets)
|
||||||
|
|
||||||
if self.type == Destination.GROUP:
|
if self.type == Destination.GROUP:
|
||||||
if hasattr(self, "prv") and self.prv != None:
|
if hasattr(self, "prv") and self.prv != None:
|
||||||
@ -529,7 +600,6 @@ class Destination:
|
|||||||
else:
|
else:
|
||||||
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
||||||
|
|
||||||
|
|
||||||
def sign(self, message):
|
def sign(self, message):
|
||||||
"""
|
"""
|
||||||
Signs information for ``RNS.Destination.SINGLE`` type destination.
|
Signs information for ``RNS.Destination.SINGLE`` type destination.
|
||||||
|
@ -26,6 +26,7 @@ import RNS
|
|||||||
import time
|
import time
|
||||||
import atexit
|
import atexit
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import threading
|
||||||
|
|
||||||
from .vendor import umsgpack as umsgpack
|
from .vendor import umsgpack as umsgpack
|
||||||
|
|
||||||
@ -49,11 +50,20 @@ class Identity:
|
|||||||
|
|
||||||
KEYSIZE = 256*2
|
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.
|
X.25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
RATCHETSIZE = 256
|
RATCHETSIZE = 256
|
||||||
|
"""
|
||||||
|
X.25519 ratchet key size in bits.
|
||||||
|
"""
|
||||||
|
|
||||||
RATCHET_EXPIRY = 60*60*24*30
|
RATCHET_EXPIRY = 60*60*24*30
|
||||||
|
"""
|
||||||
|
The expiry time for received ratchets in seconds, defaults to 30 days. Reticulum will always use the most recently
|
||||||
|
announced ratchet, and remember it for up to ``RATCHET_EXPIRY`` since receiving it, after which it will be discarded.
|
||||||
|
If a newer ratchet is announced in the meantime, it will be replace the already known ratchet.
|
||||||
|
"""
|
||||||
|
|
||||||
# Non-configurable constants
|
# Non-configurable constants
|
||||||
FERNET_OVERHEAD = RNS.Cryptography.Fernet.FERNET_OVERHEAD
|
FERNET_OVERHEAD = RNS.Cryptography.Fernet.FERNET_OVERHEAD
|
||||||
@ -72,6 +82,8 @@ class Identity:
|
|||||||
known_destinations = {}
|
known_destinations = {}
|
||||||
known_ratchets = {}
|
known_ratchets = {}
|
||||||
|
|
||||||
|
ratchet_persist_lock = threading.Lock()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remember(packet_hash, destination_hash, public_key, app_data = None):
|
def remember(packet_hash, destination_hash, public_key, app_data = None):
|
||||||
if len(public_key) != Identity.KEYSIZE//8:
|
if len(public_key) != Identity.KEYSIZE//8:
|
||||||
@ -237,33 +249,64 @@ class Identity:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _remember_ratchet(destination_hash, ratchet):
|
def _remember_ratchet(destination_hash, ratchet):
|
||||||
RNS.log(f"Remembering ratchet {RNS.prettyhexrep(Identity.truncated_hash(ratchet))} for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_DEBUG) # TODO: Remove
|
# TODO: Remove at some point
|
||||||
|
RNS.log(f"Remembering ratchet {RNS.prettyhexrep(Identity.truncated_hash(ratchet))} for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_EXTREME)
|
||||||
try:
|
try:
|
||||||
Identity.known_ratchets[destination_hash] = ratchet
|
Identity.known_ratchets[destination_hash] = ratchet
|
||||||
hexhash = RNS.hexrep(destination_hash, delimit=False)
|
|
||||||
ratchet_data = {"ratchet": ratchet, "received": time.time()}
|
|
||||||
|
|
||||||
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
|
if not RNS.Transport.owner.is_connected_to_shared_instance:
|
||||||
|
def persist_job():
|
||||||
if not os.path.isdir(ratchetdir):
|
with Identity.ratchet_persist_lock:
|
||||||
os.makedirs(ratchetdir)
|
hexhash = RNS.hexrep(destination_hash, delimit=False)
|
||||||
|
ratchet_data = {"ratchet": ratchet, "received": time.time()}
|
||||||
|
|
||||||
outpath = f"{ratchetdir}/{hexhash}.out"
|
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
|
||||||
finalpath = f"{ratchetdir}/{hexhash}"
|
|
||||||
ratchet_file = open(outpath, "wb")
|
if not os.path.isdir(ratchetdir):
|
||||||
ratchet_file.write(umsgpack.packb(ratchet_data))
|
os.makedirs(ratchetdir)
|
||||||
ratchet_file.close()
|
|
||||||
os.rename(outpath, finalpath)
|
outpath = f"{ratchetdir}/{hexhash}.out"
|
||||||
|
finalpath = f"{ratchetdir}/{hexhash}"
|
||||||
|
ratchet_file = open(outpath, "wb")
|
||||||
|
ratchet_file.write(umsgpack.packb(ratchet_data))
|
||||||
|
ratchet_file.close()
|
||||||
|
os.rename(outpath, finalpath)
|
||||||
|
|
||||||
|
|
||||||
|
threading.Thread(target=persist_job, daemon=True).start()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log(f"Could not persist ratchet for {RNS.prettyhexrep(destination_hash)} to storage.", RNS.LOG_ERROR)
|
RNS.log(f"Could not persist ratchet for {RNS.prettyhexrep(destination_hash)} to storage.", RNS.LOG_ERROR)
|
||||||
RNS.log(f"The contained exception was: {e}")
|
RNS.log(f"The contained exception was: {e}")
|
||||||
RNS.trace_exception(e)
|
RNS.trace_exception(e)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _clean_ratchets():
|
||||||
|
RNS.log("Cleaning ratchets...", RNS.LOG_DEBUG)
|
||||||
|
try:
|
||||||
|
now = time.time()
|
||||||
|
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
|
||||||
|
for filename in os.listdir(ratchetdir):
|
||||||
|
try:
|
||||||
|
expired = False
|
||||||
|
with open(f"{ratchetdir}/{filename}", "rb") as rf:
|
||||||
|
ratchet_data = umsgpack.unpackb(rf.read())
|
||||||
|
if now > ratchet_data["received"]+Identity.RATCHET_EXPIRY:
|
||||||
|
expired = True
|
||||||
|
|
||||||
|
if expired:
|
||||||
|
os.unlink(f"{ratchetdir}/{filename}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"An error occurred while cleaning ratchets, in the processing of {ratchetdir}/{filename}.", RNS.LOG_ERROR)
|
||||||
|
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"An error occurred while cleaning ratchets. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_ratchet(destination_hash):
|
def get_ratchet(destination_hash):
|
||||||
if not destination_hash in Identity.known_ratchets:
|
if not destination_hash in Identity.known_ratchets:
|
||||||
RNS.log(f"Trying to load ratchet for {RNS.prettyhexrep(destination_hash)} from storage") # TODO: Remove
|
|
||||||
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
|
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
|
||||||
hexhash = RNS.hexrep(destination_hash, delimit=False)
|
hexhash = RNS.hexrep(destination_hash, delimit=False)
|
||||||
ratchet_path = f"{ratchetdir}/hexhash"
|
ratchet_path = f"{ratchetdir}/hexhash"
|
||||||
@ -284,6 +327,7 @@ class Identity:
|
|||||||
if destination_hash in Identity.known_ratchets:
|
if destination_hash in Identity.known_ratchets:
|
||||||
return Identity.known_ratchets[destination_hash]
|
return Identity.known_ratchets[destination_hash]
|
||||||
else:
|
else:
|
||||||
|
RNS.log(f"Could not load ratchet for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_DEBUG)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -572,7 +616,8 @@ class Identity:
|
|||||||
ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes()
|
ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes()
|
||||||
|
|
||||||
if ratchet != None:
|
if ratchet != None:
|
||||||
RNS.log(f"Encrypting with ratchet {RNS.prettyhexrep(RNS.Identity.truncated_hash(ratchet))}", RNS.LOG_DEBUG) # TODO: Remove
|
# TODO: Remove at some point
|
||||||
|
RNS.log(f"Encrypting with ratchet {RNS.prettyhexrep(RNS.Identity.truncated_hash(ratchet))}", RNS.LOG_EXTREME)
|
||||||
target_public_key = X25519PublicKey.from_public_bytes(ratchet)
|
target_public_key = X25519PublicKey.from_public_bytes(ratchet)
|
||||||
else:
|
else:
|
||||||
target_public_key = self.pub
|
target_public_key = self.pub
|
||||||
@ -595,7 +640,7 @@ class Identity:
|
|||||||
raise KeyError("Encryption failed because identity does not hold a public key")
|
raise KeyError("Encryption failed because identity does not hold a public key")
|
||||||
|
|
||||||
|
|
||||||
def decrypt(self, ciphertext_token, ratchets=None):
|
def decrypt(self, ciphertext_token, ratchets=None, enforce_ratchets=False):
|
||||||
"""
|
"""
|
||||||
Decrypts information for the identity.
|
Decrypts information for the identity.
|
||||||
|
|
||||||
@ -626,14 +671,17 @@ class Identity:
|
|||||||
fernet = Fernet(derived_key)
|
fernet = Fernet(derived_key)
|
||||||
plaintext = fernet.decrypt(ciphertext)
|
plaintext = fernet.decrypt(ciphertext)
|
||||||
|
|
||||||
# TODO: Remove
|
# TODO: Remove at some point
|
||||||
RNS.log(f"Decrypted with ratchet {RNS.prettyhexrep(RNS.Identity.truncated_hash(ratchet_prv.public_key().public_bytes()))}", RNS.LOG_DEBUG)
|
RNS.log(f"Decrypted with ratchet {RNS.prettyhexrep(RNS.Identity.truncated_hash(ratchet_prv.public_key().public_bytes()))}", RNS.LOG_EXTREME)
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
# RNS.log("Decryption using this ratchet failed", RNS.LOG_DEBUG) # TODO: Remove
|
|
||||||
|
if enforce_ratchets and plaintext == None:
|
||||||
|
RNS.log("Decryption with ratchet enforcement by "+RNS.prettyhexrep(self.hash)+" failed. Dropping packet.", RNS.LOG_DEBUG)
|
||||||
|
return None
|
||||||
|
|
||||||
if plaintext == None:
|
if plaintext == None:
|
||||||
shared_key = self.prv.exchange(peer_pub)
|
shared_key = self.prv.exchange(peer_pub)
|
||||||
|
@ -295,6 +295,7 @@ class Reticulum:
|
|||||||
|
|
||||||
def __start_jobs(self):
|
def __start_jobs(self):
|
||||||
if self.jobs_thread == None:
|
if self.jobs_thread == None:
|
||||||
|
RNS.Identity._clean_ratchets()
|
||||||
self.jobs_thread = threading.Thread(target=self.__jobs)
|
self.jobs_thread = threading.Thread(target=self.__jobs)
|
||||||
self.jobs_thread.daemon = True
|
self.jobs_thread.daemon = True
|
||||||
self.jobs_thread.start()
|
self.jobs_thread.start()
|
||||||
|
Loading…
Reference in New Issue
Block a user