From 1001234fc69f4a0e6a9daee3b6493d277631c05d Mon Sep 17 00:00:00 2001 From: comms_engineer Date: Fri, 3 Jul 2026 00:31:08 +0000 Subject: [PATCH] security: reject insecure default channel_secret, validate input - MeshCore_Dynamic_Interface: replace all-zeros default channel_secret with startup validation that refuses to initialize if the secret is missing, empty, or set to the known insecure default. Also adds format (hex) and length (16 bytes) validation. - MeshCore_Channel_Interface: replace hardcoded default channel_secret (c4d2b6c8254e3b11200f57e95dcb1197) with the same startup validation. Updates docstring config examples to use a placeholder instead of a real secret value. Enhances SSL-disabled warning to explicitly mention MITM risk. These changes prevent nodes from silently running with publicly-known encryption keys, which would allow any eavesdropper to decode and inject packets on the mesh. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- Interface/MeshCore_Channel_Interface.py | 52 ++++++++++++++++++++++--- Interface/MeshCore_Dynamic_Interface.py | 40 ++++++++++++++++++- 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/Interface/MeshCore_Channel_Interface.py b/Interface/MeshCore_Channel_Interface.py index e9bd894..be46f2b 100644 --- a/Interface/MeshCore_Channel_Interface.py +++ b/Interface/MeshCore_Channel_Interface.py @@ -74,7 +74,7 @@ baudrate = 115200 channel_idx = 39 channel_name = RNSTunnel - channel_secret = c4d2b6c8254e3b11200f57e95dcb1197 + channel_secret = # openssl rand -hex 16 fragment_delay = 1.5 fragment_timeout = 3600 debug_level = debug @@ -88,7 +88,7 @@ transport = remoteterm remoteterm_url = http://localhost:8000 channel_name = RNSTunnel - channel_secret = c4d2b6c8254e3b11200f57e95dcb1197 + channel_secret = # openssl rand -hex 16 fragment_delay = 1.5 fragment_timeout = 3600 debug_level = debug @@ -103,7 +103,7 @@ remoteterm_url = https://host.docker.internal:8000 remoteterm_ssl_verify = false # set false for self-signed certs channel_name = RNSTunnel - channel_secret = c4d2b6c8254e3b11200f57e95dcb1197 + channel_secret = # openssl rand -hex 16 fragment_delay = 1.5 fragment_timeout = 3600 debug_level = debug @@ -243,7 +243,9 @@ def __init__(self, owner, configuration): self._rt_ssl_ctx = _ctx RNS.log( f"MeshCore_Channel_Interface [{self.name}]: " - "SSL certificate verification DISABLED (self-signed cert mode)", + "WARNING — SSL certificate verification DISABLED. " + "This exposes the connection to man-in-the-middle attacks. " + "Only use this for trusted networks with self-signed certs.", RNS.LOG_WARNING ) else: @@ -251,8 +253,46 @@ def __init__(self, owner, configuration): # ---- channel (both modes) ---- self.channel_name = cfg.get("channel_name", "RNSTunnel") - self.channel_secret_hex = cfg.get("channel_secret", - "c4d2b6c8254e3b11200f57e95dcb1197") + self.channel_secret_hex = cfg.get("channel_secret", "") + + # Validate channel_secret: refuse to start with a missing or insecure + # default. A known/hardcoded key means traffic is effectively + # unencrypted — any eavesdropper who reads this source can decode and + # inject packets. + _INSECURE_DEFAULTS = ( + "", + "00000000000000000000000000000000", + "c4d2b6c8254e3b11200f57e95dcb1197", # old hardcoded default + ) + if self.channel_secret_hex.lower().strip() in _INSECURE_DEFAULTS: + RNS.log( + f"MeshCore_Channel_Interface [{self.name}]: CRITICAL — " + f"'channel_secret' is missing or set to an insecure default. " + f"All nodes sharing this interface MUST use a unique secret. " + f"Generate one with: openssl rand -hex 16", + RNS.LOG_CRITICAL + ) + raise ValueError( + f"MeshCore_Channel_Interface [{self.name}]: " + "channel_secret is missing or insecure — refusing to start. " + "Set a unique 32-character hex secret (openssl rand -hex 16)." + ) + + # Validate format: must be valid hex and exactly 16 bytes (32 hex chars) + try: + _secret_bytes = bytes.fromhex(self.channel_secret_hex) + except ValueError: + raise ValueError( + f"MeshCore_Channel_Interface [{self.name}]: " + f"channel_secret is not valid hexadecimal: " + f"'{self.channel_secret_hex[:8]}...'" + ) + if len(_secret_bytes) != 16: + raise ValueError( + f"MeshCore_Channel_Interface [{self.name}]: " + f"channel_secret must be exactly 16 bytes (32 hex chars), " + f"got {len(_secret_bytes)} bytes." + ) # conversation_key as RemoteTerm reports it — populated in # _rt_ensure_channel() once we learn the actual key format RemoteTerm uses. diff --git a/Interface/MeshCore_Dynamic_Interface.py b/Interface/MeshCore_Dynamic_Interface.py index 6c2ccbd..663359e 100644 --- a/Interface/MeshCore_Dynamic_Interface.py +++ b/Interface/MeshCore_Dynamic_Interface.py @@ -330,8 +330,44 @@ def __init__(self, owner, configuration): # --- Channel identity ---------------------------------------------- self.channel_idx = int(str(cfg.get("channel_idx", 0)).strip()) self.channel_name = cfg.get("channel_name", "RNSTunnel") - self.channel_secret_hex = cfg.get("channel_secret", - "00000000000000000000000000000000") + self.channel_secret_hex = cfg.get("channel_secret", "") + + # Validate channel_secret: refuse to start with a missing or insecure + # default. A known/all-zeros key means traffic is effectively + # unencrypted — any eavesdropper can decode and inject packets. + _INSECURE_DEFAULTS = ( + "", + "00000000000000000000000000000000", + ) + if self.channel_secret_hex.lower().strip() in _INSECURE_DEFAULTS: + RNS.log( + f"MeshCore_Dynamic_Interface [{self.name}]: CRITICAL — " + f"'channel_secret' is missing or set to an insecure default. " + f"All nodes sharing this interface MUST use a unique secret. " + f"Generate one with: openssl rand -hex 16", + RNS.LOG_CRITICAL + ) + raise ValueError( + f"MeshCore_Dynamic_Interface [{self.name}]: " + "channel_secret is missing or insecure — refusing to start. " + "Set a unique 32-character hex secret (openssl rand -hex 16)." + ) + + # Validate format: must be valid hex and exactly 16 bytes (32 hex chars) + try: + _secret_bytes = bytes.fromhex(self.channel_secret_hex) + except ValueError: + raise ValueError( + f"MeshCore_Dynamic_Interface [{self.name}]: " + f"channel_secret is not valid hexadecimal: " + f"'{self.channel_secret_hex[:8]}...'" + ) + if len(_secret_bytes) != 16: + raise ValueError( + f"MeshCore_Dynamic_Interface [{self.name}]: " + f"channel_secret must be exactly 16 bytes (32 hex chars), " + f"got {len(_secret_bytes)} bytes." + ) # --- Optional radio parameter overrides ---------------------------- self.radio_freq = float(cfg.get("freq", 0))