From a44c1f368a28e7a73a4d083ec622e52865d43923 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 31 Oct 2025 17:02:34 +0100 Subject: [PATCH] Validate peering key on incoming sync offer --- LXMF/LXMPeer.py | 8 ++++++-- LXMF/LXMRouter.py | 36 +++++++++++++++++++++--------------- LXMF/LXStamper.py | 16 +++++++++++++++- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/LXMF/LXMPeer.py b/LXMF/LXMPeer.py index 0fe1e74..7d851f2 100644 --- a/LXMF/LXMPeer.py +++ b/LXMF/LXMPeer.py @@ -22,7 +22,9 @@ class LXMPeer: ERROR_NO_IDENTITY = 0xf0 ERROR_NO_ACCESS = 0xf1 - ERROR_THROTTLED = 0xf2 + ERROR_INVALID_KEY = 0xf3 + ERROR_INVALID_DATA = 0xf4 + ERROR_THROTTLED = 0xf5 ERROR_TIMEOUT = 0xfe STRATEGY_LAZY = 0x01 @@ -369,9 +371,11 @@ class LXMPeer: cumulative_size += lxm_transfer_size unhandled_ids.append(transient_id) + offer = [self.peering_key[0], unhandled_ids] + RNS.log(f"Offering {len(unhandled_ids)} messages to peer {RNS.prettyhexrep(self.destination.hash)} ({RNS.prettysize(len(msgpack.packb(unhandled_ids)))})", RNS.LOG_VERBOSE) self.last_offer = unhandled_ids - self.link.request(LXMPeer.OFFER_REQUEST_PATH, unhandled_ids, response_callback=self.offer_response, failed_callback=self.request_failed) + self.link.request(LXMPeer.OFFER_REQUEST_PATH, offer, response_callback=self.offer_response, failed_callback=self.request_failed) self.state = LXMPeer.REQUEST_SENT else: diff --git a/LXMF/LXMRouter.py b/LXMF/LXMRouter.py index ce6b685..bac498b 100644 --- a/LXMF/LXMRouter.py +++ b/LXMF/LXMRouter.py @@ -45,8 +45,8 @@ class LXMRouter: ROTATION_HEADROOM_PCT = 10 ROTATION_AR_MAX = 0.5 - PEERING_COST = 10 - MAX_PEERING_COST = 12 + PEERING_COST = 18 + MAX_PEERING_COST = 24 PROPAGATION_COST_MIN = 13 PROPAGATION_COST_FLEX = 3 PROPAGATION_COST = 16 @@ -2047,21 +2047,30 @@ class LXMRouter: return LXMPeer.ERROR_NO_ACCESS try: - transient_ids = data - wanted_ids = [] + if type(data) != list and len(data) < 2: return LXMPeer.ERROR_INVALID_DATA - for transient_id in transient_ids: - if not transient_id in self.propagation_entries: - wanted_ids.append(transient_id) + peering_id = self.identity.hash+remote_identity + target_cost = self.peering_cost + peering_key = data[0] + transient_ids = data[1] + wanted_ids = [] - if len(wanted_ids) == 0: - return False + ts = time.time() + peering_key_valid = LXStamper.validate_peering_key(peering_id, peering_key, target_cost) + td = time.time() - ts - elif len(wanted_ids) == len(transient_ids): - return True + if not peering_key_valid: + RNS.log(f"Invalid peering key for incoming sync offer", RNS.LOG_DEBUG) + return LXMPeer.ERROR_INVALID_KEY else: - return wanted_ids + RNS.log(f"Peering key validated for incoming offer in {RNS.prettytime(td)}", RNS.LOG_DEBUG) + for transient_id in transient_ids: + if not transient_id in self.propagation_entries: wanted_ids.append(transient_id) + + if len(wanted_ids) == 0: return False + elif len(wanted_ids) == len(transient_ids): return True + else: return wanted_ids except Exception as e: RNS.log("Error occurred while generating response for sync request, the contained exception was: "+str(e), RNS.LOG_DEBUG) @@ -2069,9 +2078,6 @@ class LXMRouter: def propagation_resource_concluded(self, resource): if resource.status == RNS.Resource.COMPLETE: - # TODO: The peer this was received from should - # have the transient id added to its list of - # already handled messages. try: data = msgpack.unpackb(resource.data.read()) diff --git a/LXMF/LXStamper.py b/LXMF/LXStamper.py index 4d2e38c..2a74295 100644 --- a/LXMF/LXStamper.py +++ b/LXMF/LXStamper.py @@ -8,8 +8,8 @@ import itertools import multiprocessing WORKBLOCK_EXPAND_ROUNDS = 3000 -WORKBLOCK_EXPAND_ROUNDS_PEERING = 20000 WORKBLOCK_EXPAND_ROUNDS_PN = 1000 +WORKBLOCK_EXPAND_ROUNDS_PEERING = 25 STAMP_SIZE = RNS.Identity.HASHLENGTH PN_VALIDATION_POOL_MIN_SIZE = 256 @@ -45,6 +45,11 @@ def stamp_valid(stamp, target_cost, workblock): if int.from_bytes(result, byteorder="big") > target: return False else: return True +def validate_peering_key(peering_id, peering_key, target_cost): + workblock = stamp_workblock(peering_id, expand_rounds=WORKBLOCK_EXPAND_ROUNDS_PEERING) + if not stamp_valid(peering_key, target_cost, workblock): return False + else: return True + def validate_pn_stamp(transient_data, target_cost): from .LXMessage import LXMessage if len(transient_data) <= LXMessage.LXMF_OVERHEAD+STAMP_SIZE: return False, None, None @@ -348,6 +353,13 @@ def job_android(stamp_cost, workblock, message_id): return stamp, total_rounds +# def stamp_value_linear(workblock, stamp): +# value = 0 +# bits = 256 +# material = RNS.Identity.full_hash(workblock+stamp) +# s = int.from_bytes(material, byteorder="big") +# return s.bit_count() + if __name__ == "__main__": import sys if len(sys.argv) < 2: @@ -365,10 +377,12 @@ if __name__ == "__main__": message_id = os.urandom(32) generate_stamp(message_id, cost) + RNS.log("", RNS.LOG_DEBUG) RNS.log("Testing propagation stamp generation", RNS.LOG_DEBUG) message_id = os.urandom(32) generate_stamp(message_id, cost, expand_rounds=WORKBLOCK_EXPAND_ROUNDS_PN) + RNS.log("", RNS.LOG_DEBUG) RNS.log("Testing peering key generation", RNS.LOG_DEBUG) message_id = os.urandom(32) generate_stamp(message_id, cost, expand_rounds=WORKBLOCK_EXPAND_ROUNDS_PEERING) \ No newline at end of file