Make ratchet IDs available to applications

This commit is contained in:
Mark Qvist 2024-09-08 14:55:07 +02:00
parent fe054fd03c
commit 3a580e74de
5 changed files with 28 additions and 15 deletions

View File

@ -154,6 +154,7 @@ class Destination:
self.ratchet_interval = Destination.RATCHET_INTERVAL self.ratchet_interval = Destination.RATCHET_INTERVAL
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.__enforce_ratchets = False self.__enforce_ratchets = False
self.mtu = 0 self.mtu = 0
@ -401,6 +402,7 @@ class Destination:
self.incoming_link_request(plaintext, packet) self.incoming_link_request(plaintext, packet)
else: else:
plaintext = self.decrypt(packet.data) plaintext = self.decrypt(packet.data)
packet.ratchet_id = self.latest_ratchet_id
if plaintext != None: if plaintext != None:
if packet.packet_type == RNS.Packet.DATA: if packet.packet_type == RNS.Packet.DATA:
if self.callbacks.packet != None: if self.callbacks.packet != None:
@ -565,7 +567,9 @@ class Destination:
return plaintext return plaintext
if self.type == Destination.SINGLE and self.identity != None: if self.type == Destination.SINGLE and self.identity != None:
return self.identity.encrypt(plaintext, 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)
return self.identity.encrypt(plaintext, ratchet=selected_ratchet)
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:
@ -588,7 +592,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, enforce_ratchets=self.__enforce_ratchets) return self.identity.decrypt(ciphertext, ratchets=self.ratchets, 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

@ -631,8 +631,6 @@ 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:
# 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
@ -655,7 +653,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, enforce_ratchets=False): def decrypt(self, ciphertext_token, ratchets=None, enforce_ratchets=False, ratchet_id_receiver=None):
""" """
Decrypts information for the identity. Decrypts information for the identity.
@ -675,6 +673,7 @@ class Identity:
for ratchet in ratchets: for ratchet in ratchets:
try: try:
ratchet_prv = X25519PrivateKey.from_private_bytes(ratchet) ratchet_prv = X25519PrivateKey.from_private_bytes(ratchet)
ratchet_id = Identity.truncated_hash(ratchet_prv.public_key().public_bytes())
shared_key = ratchet_prv.exchange(peer_pub) shared_key = ratchet_prv.exchange(peer_pub)
derived_key = RNS.Cryptography.hkdf( derived_key = RNS.Cryptography.hkdf(
length=32, length=32,
@ -685,9 +684,8 @@ class Identity:
fernet = Fernet(derived_key) fernet = Fernet(derived_key)
plaintext = fernet.decrypt(ciphertext) plaintext = fernet.decrypt(ciphertext)
if ratchet_id_receiver:
# TODO: Remove at some point ratchet_id_receiver.latest_ratchet_id = ratchet_id
RNS.log(f"Decrypted with ratchet {RNS.prettyhexrep(RNS.Identity.truncated_hash(ratchet_prv.public_key().public_bytes()))}", RNS.LOG_EXTREME)
break break
@ -696,6 +694,8 @@ class Identity:
if enforce_ratchets and plaintext == None: if enforce_ratchets and plaintext == None:
RNS.log("Decryption with ratchet enforcement by "+RNS.prettyhexrep(self.hash)+" failed. Dropping packet.", RNS.LOG_DEBUG) RNS.log("Decryption with ratchet enforcement by "+RNS.prettyhexrep(self.hash)+" failed. Dropping packet.", RNS.LOG_DEBUG)
if ratchet_id_receiver:
ratchet_id_receiver.latest_ratchet_id = None
return None return None
if plaintext == None: if plaintext == None:
@ -709,9 +709,13 @@ class Identity:
fernet = Fernet(derived_key) fernet = Fernet(derived_key)
plaintext = fernet.decrypt(ciphertext) plaintext = fernet.decrypt(ciphertext)
if ratchet_id_receiver:
ratchet_id_receiver.latest_ratchet_id = None
except Exception as e: except Exception as e:
RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed: "+str(e), RNS.LOG_DEBUG) RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed: "+str(e), RNS.LOG_DEBUG)
if ratchet_id_receiver:
ratchet_id_receiver.latest_ratchet_id = None
return plaintext; return plaintext;
else: else:

View File

@ -775,6 +775,7 @@ class Link:
should_query = False should_query = False
if packet.context == RNS.Packet.NONE: if packet.context == RNS.Packet.NONE:
plaintext = self.decrypt(packet.data) plaintext = self.decrypt(packet.data)
packet.ratchet_id = self.link_id
if plaintext != None: if plaintext != None:
if self.callbacks.packet != None: if self.callbacks.packet != None:
thread = threading.Thread(target=self.callbacks.packet, args=(plaintext, packet)) thread = threading.Thread(target=self.callbacks.packet, args=(plaintext, packet))

View File

@ -140,6 +140,7 @@ class Packet:
self.MTU = RNS.Reticulum.MTU self.MTU = RNS.Reticulum.MTU
self.sent_at = None self.sent_at = None
self.packet_hash = None self.packet_hash = None
self.ratchet_id = None
self.attached_interface = attached_interface self.attached_interface = attached_interface
self.receiving_interface = None self.receiving_interface = None
@ -195,6 +196,8 @@ class Packet:
# In all other cases, we encrypt the packet # In all other cases, we encrypt the packet
# with the destination's encryption method # with the destination's encryption method
self.ciphertext = self.destination.encrypt(self.data) self.ciphertext = self.destination.encrypt(self.data)
if hasattr(self.destination, "latest_ratchet_id"):
self.ratchet_id = self.destination.latest_ratchet_id
if self.header_type == Packet.HEADER_2: if self.header_type == Packet.HEADER_2:
if self.transport_id != None: if self.transport_id != None:
@ -418,6 +421,7 @@ class PacketReceipt:
except Exception as e: except Exception as e:
RNS.log("An error occurred while evaluating external delivery callback for "+str(link), RNS.LOG_ERROR) RNS.log("An error occurred while evaluating external delivery callback for "+str(link), RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e)
return True return True
else: else:

View File

@ -52,16 +52,16 @@ class Transport:
Maximum amount of hops that Reticulum will transport a packet. Maximum amount of hops that Reticulum will transport a packet.
""" """
PATHFINDER_R = 1 # Retransmit retries PATHFINDER_R = 1 # Retransmit retries
PATHFINDER_G = 5 # Retry grace period PATHFINDER_G = 5 # Retry grace period
PATHFINDER_RW = 0.5 # Random window for announce rebroadcast PATHFINDER_RW = 0.5 # Random window for announce rebroadcast
PATHFINDER_E = 60*60*24*7 # Path expiration of one week PATHFINDER_E = 60*60*24*7 # Path expiration of one week
AP_PATH_TIME = 60*60*24 # Path expiration of one day for Access Point paths AP_PATH_TIME = 60*60*24 # Path expiration of one day for Access Point paths
ROAMING_PATH_TIME = 60*60*6 # Path expiration of 6 hours for Roaming paths ROAMING_PATH_TIME = 60*60*6 # Path expiration of 6 hours for Roaming paths
# TODO: Calculate an optimal number for this in # TODO: Calculate an optimal number for this in
# various situations # various situations
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
PATH_REQUEST_TIMEOUT = 15 # Default timuout for client path requests in seconds PATH_REQUEST_TIMEOUT = 15 # Default timuout for client path requests in seconds
PATH_REQUEST_GRACE = 0.4 # Grace time before a path announcement is made, allows directly reachable peers to respond first PATH_REQUEST_GRACE = 0.4 # Grace time before a path announcement is made, allows directly reachable peers to respond first