Added automatic ratchet reload if required ratchet is unavailable
This commit is contained in:
parent
3a580e74de
commit
a072a5b074
|
@ -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,6 +198,7 @@ class Destination:
|
||||||
|
|
||||||
def _persist_ratchets(self):
|
def _persist_ratchets(self):
|
||||||
try:
|
try:
|
||||||
|
with self.ratchet_file_lock:
|
||||||
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}
|
||||||
ratchets_file = open(self.ratchets_path, "wb")
|
ratchets_file = open(self.ratchets_path, "wb")
|
||||||
|
@ -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,24 +417,9 @@ class Destination:
|
||||||
if link != None:
|
if link != None:
|
||||||
self.links.append(link)
|
self.links.append(link)
|
||||||
|
|
||||||
def enable_ratchets(self, ratchets_path):
|
def _reload_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):
|
if os.path.isfile(ratchets_path):
|
||||||
|
with self.ratchet_file_lock:
|
||||||
try:
|
try:
|
||||||
ratchets_file = open(ratchets_path, "rb")
|
ratchets_file = open(ratchets_path, "rb")
|
||||||
persisted_data = umsgpack.unpackb(ratchets_file.read())
|
persisted_data = umsgpack.unpackb(ratchets_file.read())
|
||||||
|
@ -455,6 +440,25 @@ class Destination:
|
||||||
self.ratchets_path = ratchets_path
|
self.ratchets_path = ratchets_path
|
||||||
self._persist_ratchets()
|
self._persist_ratchets()
|
||||||
|
|
||||||
|
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
|
||||||
|
self._reload_ratchets(ratchets_path)
|
||||||
|
|
||||||
# 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)
|
||||||
return True
|
return True
|
||||||
|
@ -568,6 +572,7 @@ 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)
|
||||||
|
if selected_ratchet:
|
||||||
self.latest_ratchet_id = RNS.Identity.truncated_hash(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)
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue