From 5d90ea565a8d3f640285d9b490d404d539eab699 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 27 Apr 2022 19:00:09 +0200 Subject: [PATCH] Implemented interface authentication and virtual network segmentation --- RNS/Reticulum.py | 76 ++++++++++++++++++++++++++------------- RNS/Transport.py | 56 ++++++++++++++++++++++++++++- RNS/Utilities/rnstatus.py | 8 +++++ RNS/__init__.py | 5 +-- 4 files changed, 117 insertions(+), 28 deletions(-) diff --git a/RNS/Reticulum.py b/RNS/Reticulum.py index ee33083..ea53ea8 100755 --- a/RNS/Reticulum.py +++ b/RNS/Reticulum.py @@ -21,6 +21,11 @@ # SOFTWARE. from .vendor.platformutils import get_platform +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDF +from cryptography.hazmat.backends import default_backend + +cio_default_backend = default_backend() if get_platform() == "android": from .Interfaces import Interface @@ -122,6 +127,7 @@ class Reticulum: HEADER_MINSIZE = 2+1+(TRUNCATED_HASHLENGTH//8)*1 HEADER_MAXSIZE = 2+1+(TRUNCATED_HASHLENGTH//8)*2 IFAC_MIN_SIZE = 1 + IFAC_SALT = bytes.fromhex("adf54d882c9a9b80771eb4995d702d4a3e733391b2a0f53f416d9f907e55cff8") MDU = MTU - HEADER_MAXSIZE - IFAC_MIN_SIZE @@ -186,6 +192,8 @@ class Reticulum: self.share_instance = True self.rpc_listener = None + self.ifac_salt = Reticulum.IFAC_SALT + self.requested_loglevel = loglevel if self.requested_loglevel != None: if self.requested_loglevel > RNS.LOG_EXTREME: @@ -356,14 +364,20 @@ class Reticulum: ifac_size = c.as_int("ifac_size") ifac_netname = None - if "ifac_netname" in c: - if c.as_int("ifac_netname") >= Reticulum.IFAC_MIN_SIZE: - ifac_netname = c.as_int("ifac_netname") - + if "networkname" in c: + if c["networkname"] != "": + ifac_netname = c["networkname"] + if "network_name" in c: + if c["network_name"] != "": + ifac_netname = c["network_name"] + ifac_netkey = None - if "ifac_netkey" in c: - if c.as_int("ifac_netkey") >= Reticulum.IFAC_MIN_SIZE: - ifac_netkey = c.as_int("ifac_netkey") + if "passphrase" in c: + if c["passphrase"] != "": + ifac_netkey = c["passphrase"] + if "pass_phrase" in c: + if c["pass_phrase"] != "": + ifac_netkey = c["pass_phrase"] configured_bitrate = None if "bitrate" in c: @@ -406,8 +420,6 @@ class Reticulum: interface.mode = interface_mode - RNS.Transport.interfaces.append(interface) - interface.announce_cap = announce_cap if configured_bitrate: interface.bitrate = configured_bitrate @@ -452,8 +464,6 @@ class Reticulum: interface.mode = interface_mode - RNS.Transport.interfaces.append(interface) - interface.announce_cap = announce_cap if configured_bitrate: interface.bitrate = configured_bitrate @@ -492,8 +502,6 @@ class Reticulum: interface.mode = interface_mode - RNS.Transport.interfaces.append(interface) - interface.announce_cap = announce_cap if configured_bitrate: interface.bitrate = configured_bitrate @@ -529,8 +537,6 @@ class Reticulum: interface.mode = interface_mode - RNS.Transport.interfaces.append(interface) - interface.announce_cap = announce_cap if configured_bitrate: interface.bitrate = configured_bitrate @@ -562,8 +568,6 @@ class Reticulum: interface.mode = interface_mode - RNS.Transport.interfaces.append(interface) - interface.announce_cap = announce_cap if configured_bitrate: interface.bitrate = configured_bitrate @@ -599,8 +603,6 @@ class Reticulum: interface.mode = interface_mode - RNS.Transport.interfaces.append(interface) - interface.announce_cap = announce_cap if configured_bitrate: interface.bitrate = configured_bitrate @@ -650,8 +652,6 @@ class Reticulum: interface.mode = interface_mode - RNS.Transport.interfaces.append(interface) - interface.announce_cap = announce_cap if configured_bitrate: interface.bitrate = configured_bitrate @@ -702,8 +702,6 @@ class Reticulum: interface.mode = interface_mode - RNS.Transport.interfaces.append(interface) - interface.announce_cap = announce_cap if configured_bitrate: interface.bitrate = configured_bitrate @@ -748,8 +746,6 @@ class Reticulum: interface.mode = interface_mode - RNS.Transport.interfaces.append(interface) - interface.announce_cap = announce_cap if configured_bitrate: interface.bitrate = configured_bitrate @@ -762,6 +758,27 @@ class Reticulum: interface.ifac_netname = ifac_netname interface.ifac_netkey = ifac_netkey + if interface.ifac_netname != None or interface.ifac_netkey != None: + ifac_origin = b"" + + if interface.ifac_netname != None: + ifac_origin += RNS.Identity.full_hash(interface.ifac_netname.encode("utf-8")) + + if interface.ifac_netkey != None: + ifac_origin += RNS.Identity.full_hash(interface.ifac_netkey.encode("utf-8")) + + ifac_origin_hash = RNS.Identity.full_hash(ifac_origin) + interface.ifac_key = HKDF( + algorithm=hashes.SHA256(), + length=64, + salt=self.ifac_salt, + info=None, + backend=cio_default_backend, + ).derive(ifac_origin_hash) + + interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key) + interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key)) + RNS.Transport.interfaces.append(interface) else: @@ -859,6 +876,15 @@ class Reticulum: else: ifstats["peers"] = None + if hasattr(interface, "ifac_signature"): + ifstats["ifac_signature"] = interface.ifac_signature + ifstats["ifac_size"] = interface.ifac_size + ifstats["ifac_netname"] = interface.ifac_netname + else: + ifstats["ifac_signature"] = None + ifstats["ifac_size"] = None + ifstats["ifac_netname"] = None + if hasattr(interface, "announce_queue"): if interface.announce_queue != None: ifstats["announce_queue"] = len(interface.announce_queue) diff --git a/RNS/Transport.py b/RNS/Transport.py index 78948e4..cc773e1 100755 --- a/RNS/Transport.py +++ b/RNS/Transport.py @@ -474,7 +474,23 @@ class Transport: @staticmethod def transmit(interface, raw): - interface.processOutgoing(raw) + try: + if hasattr(interface, "ifac_identity") and interface.ifac_identity != None: + # Calculate packet access code + ifac = interface.ifac_identity.sign(raw)[-interface.ifac_size:] + + # Set IFAC flag + new_header = bytes([raw[0] | 0x80, raw[1]]) + + # Assemble new payload with IFAC and send it + new_raw = new_header+ifac+raw[2:] + interface.processOutgoing(new_raw) + + else: + interface.processOutgoing(raw) + + except Exception as e: + RNS.log("Error while transmitting on "+str(interface)+". The contained exception was: "+str(e), RNS.LOG_ERROR) @staticmethod def outbound(packet): @@ -704,6 +720,44 @@ class Transport: @staticmethod def inbound(raw, interface=None): + # If interface access codes are enabled, + # we must authenticate each packet. + if interface != None and hasattr(interface, "ifac_identity") and interface.ifac_identity != None: + # Check that IFAC flag is set + if raw[0] & 0x80 == 0x80: + if len(raw) > 2+interface.ifac_size: + # Extract IFAC + ifac = raw[2:2+interface.ifac_size] + + # Unset IFAC flag + new_header = bytes([raw[0] & 0x7f, raw[1]]) + + # Re-assemble packet + new_raw = new_header+raw[2+interface.ifac_size:] + + # Calculate expected IFAC + expected_ifac = interface.ifac_identity.sign(new_raw)[-interface.ifac_size:] + + # Check it + if ifac == expected_ifac: + # TODO: Remove log statements + RNS.log("Packet IFAC match, allowing", RNS.LOG_EXTREME) + raw = new_raw + else: + # TODO: Remove log statements + RNS.log("Packet IFAC mismatch, dropping packet", RNS.LOG_EXTREME) + return + + else: + return + + else: + # If the IFAC flag is not set, but should be, + # we drop the packet. + # TODO: Remove log statements + RNS.log(str(interface)+" with IFAC enabled received packet without access code, dropping.", RNS.LOG_EXTREME) + return + while (Transport.jobs_running): sleep(0.01) diff --git a/RNS/Utilities/rnstatus.py b/RNS/Utilities/rnstatus.py index 47038ac..b9492b9 100644 --- a/RNS/Utilities/rnstatus.py +++ b/RNS/Utilities/rnstatus.py @@ -92,6 +92,10 @@ def program_setup(configdir, dispall=False, verbosity = 0): clients = None print(" {n}".format(n=ifstat["name"])) + + if "ifac_netname" in ifstat and ifstat["ifac_netname"] != None: + print(" Network : {nn}".format(nn=ifstat["ifac_netname"])) + print(" Status : {ss}".format(ss=ss)) if clients != None: @@ -105,6 +109,10 @@ def program_setup(configdir, dispall=False, verbosity = 0): if "peers" in ifstat and ifstat["peers"] != None: print(" Peers : {np} reachable".format(np=ifstat["peers"])) + + if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None: + sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">" + print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr)) if "i2p_b32" in ifstat and ifstat["i2p_b32"] != None: print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"]))) diff --git a/RNS/__init__.py b/RNS/__init__.py index 657a0b2..7bf25d7 100755 --- a/RNS/__init__.py +++ b/RNS/__init__.py @@ -60,7 +60,8 @@ logfile = None logdest = LOG_STDOUT logtimefmt = "%Y-%m-%d %H:%M:%S" -random.seed(os.urandom(10)) +instance_random = random.Random() +instance_random.seed(os.urandom(10)) _always_override_destination = False @@ -130,7 +131,7 @@ def log(msg, level=3, _override_destination = False): def rand(): - result = random.random() + result = instance_random.random() return result def hexrep(data, delimit=True):