Added automatic ratchet reload if required ratchet is unavailable

This commit is contained in:
Mark Qvist 2024-09-08 17:48:25 +02:00
parent 3a580e74de
commit a072a5b074
2 changed files with 58 additions and 32 deletions

View File

@ -23,6 +23,7 @@
import os import os
import math import math
import time import time
import threading
import RNS import RNS
from RNS.Cryptography import Fernet from RNS.Cryptography import Fernet
@ -152,6 +153,7 @@ class Destination:
self.ratchets = None self.ratchets = None
self.ratchets_path = None self.ratchets_path = None
self.ratchet_interval = Destination.RATCHET_INTERVAL self.ratchet_interval = Destination.RATCHET_INTERVAL
self.ratchet_file_lock = threading.Lock()
self.retained_ratchets = Destination.RATCHET_COUNT self.retained_ratchets = Destination.RATCHET_COUNT
self.latest_ratchet_time = None self.latest_ratchet_time = None
self.latest_ratchet_id = None self.latest_ratchet_id = None
@ -196,11 +198,12 @@ class Destination:
def _persist_ratchets(self): def _persist_ratchets(self):
try: try:
packed_ratchets = umsgpack.packb(self.ratchets) with self.ratchet_file_lock:
persisted_data = {"signature": self.sign(packed_ratchets), "ratchets": packed_ratchets} packed_ratchets = umsgpack.packb(self.ratchets)
ratchets_file = open(self.ratchets_path, "wb") persisted_data = {"signature": self.sign(packed_ratchets), "ratchets": packed_ratchets}
ratchets_file.write(umsgpack.packb(persisted_data)) ratchets_file = open(self.ratchets_path, "wb")
ratchets_file.close() ratchets_file.write(umsgpack.packb(persisted_data))
ratchets_file.close()
except Exception as e: except Exception as e:
self.ratchets = None self.ratchets = None
self.ratchets_path = None self.ratchets_path = None
@ -267,9 +270,6 @@ 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 at some point
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):
app_data = self.default_app_data app_data = self.default_app_data
@ -417,6 +417,29 @@ class Destination:
if link != None: if link != None:
self.links.append(link) self.links.append(link)
def _reload_ratchets(self, ratchets_path):
if os.path.isfile(ratchets_path):
with self.ratchet_file_lock:
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()
def enable_ratchets(self, ratchets_path): def enable_ratchets(self, ratchets_path):
""" """
Enables ratchets on the destination. When ratchets are enabled, Reticulum will automatically rotate Enables ratchets on the destination. When ratchets are enabled, Reticulum will automatically rotate
@ -434,26 +457,7 @@ class Destination:
""" """
if ratchets_path != None: if ratchets_path != None:
self.latest_ratchet_time = 0 self.latest_ratchet_time = 0
if os.path.isfile(ratchets_path): self._reload_ratchets(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 # TODO: Remove at some point
RNS.log("Ratchets enabled on "+str(self), RNS.LOG_DEBUG) RNS.log("Ratchets enabled on "+str(self), RNS.LOG_DEBUG)
@ -568,7 +572,8 @@ class Destination:
if self.type == Destination.SINGLE and self.identity != None: if self.type == Destination.SINGLE and self.identity != None:
selected_ratchet = RNS.Identity.get_ratchet(self.hash) selected_ratchet = RNS.Identity.get_ratchet(self.hash)
self.latest_ratchet_id = RNS.Identity.truncated_hash(selected_ratchet) if selected_ratchet:
self.latest_ratchet_id = RNS.Identity.truncated_hash(selected_ratchet)
return self.identity.encrypt(plaintext, ratchet=selected_ratchet) return self.identity.encrypt(plaintext, ratchet=selected_ratchet)
if self.type == Destination.GROUP: if self.type == Destination.GROUP:
@ -592,7 +597,28 @@ 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, enforce_ratchets=self.__enforce_ratchets, ratchet_id_receiver=self) if self.ratchets:
decrypted = None
try:
decrypted = self.identity.decrypt(ciphertext, ratchets=self.ratchets, enforce_ratchets=self.__enforce_ratchets, ratchet_id_receiver=self)
except:
decrypted = None
if not decrypted:
try:
RNS.log(f"Decryption with ratchets failed on {self}, reloading ratchets from storage and retrying", RNS.LOG_ERROR)
self._reload_ratchets(self.ratchets_path)
decrypted = self.identity.decrypt(ciphertext, ratchets=self.ratchets, enforce_ratchets=self.__enforce_ratchets, ratchet_id_receiver=self)
except Exception as e:
RNS.log(f"Decryption still failing after ratchet reload. The contained exception was: {e}", RNS.LOG_ERROR)
raise e
RNS.log("Decryption succeeded after ratchet reload", RNS.LOG_NOTICE)
return decrypted
else:
return self.identity.decrypt(ciphertext, ratchets=None, enforce_ratchets=self.__enforce_ratchets, ratchet_id_receiver=self)
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:

View File

@ -324,11 +324,11 @@ class Identity:
if not destination_hash in Identity.known_ratchets: if not destination_hash in Identity.known_ratchets:
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}"
if os.path.isfile(ratchet_path): if os.path.isfile(ratchet_path):
try: try:
ratchet_file = open(ratchet_path, "rb") ratchet_file = open(ratchet_path, "rb")
ratchet_data = umsgpack.unpackb(ratchets_file.read()) ratchet_data = umsgpack.unpackb(ratchet_file.read())
if time.time() < ratchet_data["received"]+Identity.RATCHET_EXPIRY and len(ratchet_data["ratchet"]) == Identity.RATCHETSIZE//8: if time.time() < ratchet_data["received"]+Identity.RATCHET_EXPIRY and len(ratchet_data["ratchet"]) == Identity.RATCHETSIZE//8:
Identity.known_ratchets[destination_hash] = ratchet_data["ratchet"] Identity.known_ratchets[destination_hash] = ratchet_data["ratchet"]
else: else: