diff options
| author | Gab <24553253+gabrix73@users.noreply.github.com> | 2025-03-08 14:59:35 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-08 14:59:35 +0100 |
| commit | 4a3ae9929f4657a0f2e9ccf393205655895bb501 (patch) | |
| tree | cc4e8089f178b791875a9a3b34f842cf7466ba4f | |
| parent | 489f247cffd1a63936a884c6d336b318179b39d8 (diff) | |
| download | securewhisper-4a3ae9929f4657a0f2e9ccf393205655895bb501.tar.gz securewhisper-4a3ae9929f4657a0f2e9ccf393205655895bb501.tar.xz securewhisper-4a3ae9929f4657a0f2e9ccf393205655895bb501.zip | |
Add files via upload
| -rw-r--r-- | icon.png | bin | 0 -> 149382 bytes | |||
| -rw-r--r-- | mesh_chat.py | 121 | ||||
| -rw-r--r-- | network/__init__.py | 9 | ||||
| -rw-r--r-- | network/__pycache__/__init__.cpython-313.pyc | bin | 0 -> 392 bytes | |||
| -rw-r--r-- | network/__pycache__/mesh.cpython-313.pyc | bin | 0 -> 20340 bytes | |||
| -rw-r--r-- | network/__pycache__/tor_manager.cpython-313.pyc | bin | 0 -> 10264 bytes | |||
| -rw-r--r-- | network/mesh.py | 304 | ||||
| -rw-r--r-- | network/tor_manager.py | 196 | ||||
| -rw-r--r-- | security/__init__.py | 9 | ||||
| -rw-r--r-- | security/__pycache__/__init__.cpython-313.pyc | bin | 0 -> 398 bytes | |||
| -rw-r--r-- | security/__pycache__/crypto.cpython-313.pyc | bin | 0 -> 2391 bytes | |||
| -rw-r--r-- | security/__pycache__/memory.cpython-313.pyc | bin | 0 -> 2155 bytes | |||
| -rw-r--r-- | security/crypto.py | 37 | ||||
| -rw-r--r-- | security/memory.py | 38 | ||||
| -rw-r--r-- | test_health.py | 61 | ||||
| -rw-r--r-- | tor/torrc | 5 | ||||
| -rw-r--r-- | tor_data/torrc | 13 | ||||
| -rw-r--r-- | ui/__init__.py | 8 | ||||
| -rw-r--r-- | ui/__pycache__/__init__.cpython-313.pyc | bin | 0 -> 335 bytes | |||
| -rw-r--r-- | ui/__pycache__/chat_window.cpython-313.pyc | bin | 0 -> 8603 bytes | |||
| -rw-r--r-- | ui/chat_window.py | 159 |
21 files changed, 960 insertions, 0 deletions
diff --git a/icon.png b/icon.png Binary files differnew file mode 100644 index 0000000..28413ee --- /dev/null +++ b/icon.png diff --git a/mesh_chat.py b/mesh_chat.py new file mode 100644 index 0000000..f44edc9 --- /dev/null +++ b/mesh_chat.py @@ -0,0 +1,121 @@ + +import os +import sys +import asyncio +import logging +import socket +from network.tor_manager import TorManager +from network.mesh import MeshNetwork +from ui.chat_window import ChatWindow +from security.memory import SecureMemory +from security.crypto import CryptoManager + +class MeshChat: + def __init__(self): + # Configure logging + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + self.logger = logging.getLogger('MeshChat') + + # Initialize components + self.tor_manager = TorManager() + self.mesh_network = MeshNetwork(base_port=12345, max_retry_ports=5) + self.crypto = CryptoManager() + self.secure_memory = SecureMemory() + + async def _verify_network(self) -> bool: + """Verify network components are running""" + try: + # Try connecting to health check endpoint + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('127.0.0.1', 12345)) + sock.close() + self.logger.info("Network connectivity verified") + return True + except Exception as e: + self.logger.error(f"Network verification failed: {str(e)}") + return False + + async def start(self): + """Start application with robust error handling""" + try: + # Start Tor + self.logger.info("Starting Tor...") + await self.tor_manager.start() + self.logger.info(f"Tor started with onion address: {self.tor_manager.onion_address}") + + # Initialize mesh network with retries + max_retries = 3 + retry_delay = 2 + last_error = None + + for attempt in range(max_retries): + try: + self.logger.info(f"Starting mesh network (attempt {attempt + 1}/{max_retries})...") + await self.mesh_network.start() + + # Verify network is running + if await self._verify_network(): + self.logger.info("Mesh network started and verified") + break + else: + raise Exception("Network verification failed") + + except Exception as e: + last_error = e + self.logger.warning(f"Attempt {attempt + 1} failed: {str(e)}") + if attempt < max_retries - 1: + await asyncio.sleep(retry_delay) + else: + raise Exception(f"Failed to start mesh network after {max_retries} attempts: {str(last_error)}") + + # Start GUI + self.logger.info("Initializing GUI...") + self.window = ChatWindow( + self.mesh_network, + self.tor_manager, + self.crypto, + self.secure_memory + ) + + self.logger.info("Application fully started") + await self.window.run() + + except Exception as e: + self.logger.error(f"Fatal error: {str(e)}") + await self.cleanup() + sys.exit(1) + + async def cleanup(self): + """Cleanup with proper error handling""" + self.logger.info("Starting cleanup...") + + cleanup_tasks = [] + + # Stop Tor + if hasattr(self, 'tor_manager'): + cleanup_tasks.append(self.tor_manager.stop()) + + # Stop mesh network + if hasattr(self, 'mesh_network'): + cleanup_tasks.append(self.mesh_network.stop()) + + # Wipe sensitive data + if hasattr(self, 'secure_memory'): + self.secure_memory.wipe_all() + + # Wait for all cleanup tasks + if cleanup_tasks: + await asyncio.gather(*cleanup_tasks, return_exceptions=True) + + self.logger.info("Cleanup completed") + +if __name__ == "__main__": + app = MeshChat() + try: + asyncio.run(app.start()) + except KeyboardInterrupt: + app.logger.info("Received shutdown signal") + asyncio.run(app.cleanup()) diff --git a/network/__init__.py b/network/__init__.py new file mode 100644 index 0000000..5df22ef --- /dev/null +++ b/network/__init__.py @@ -0,0 +1,9 @@ +""" +Network package for P2P/Mesh chat application. +Contains Tor and mesh networking functionality. +""" + +from .tor_manager import TorManager +from .mesh import MeshNetwork + +__all__ = ['TorManager', 'MeshNetwork'] diff --git a/network/__pycache__/__init__.cpython-313.pyc b/network/__pycache__/__init__.cpython-313.pyc Binary files differnew file mode 100644 index 0000000..b5f78e8 --- /dev/null +++ b/network/__pycache__/__init__.cpython-313.pyc diff --git a/network/__pycache__/mesh.cpython-313.pyc b/network/__pycache__/mesh.cpython-313.pyc Binary files differnew file mode 100644 index 0000000..a563aad --- /dev/null +++ b/network/__pycache__/mesh.cpython-313.pyc diff --git a/network/__pycache__/tor_manager.cpython-313.pyc b/network/__pycache__/tor_manager.cpython-313.pyc Binary files differnew file mode 100644 index 0000000..fa8b4cc --- /dev/null +++ b/network/__pycache__/tor_manager.cpython-313.pyc diff --git a/network/mesh.py b/network/mesh.py new file mode 100644 index 0000000..f1f65ab --- /dev/null +++ b/network/mesh.py @@ -0,0 +1,304 @@ +import asyncio +from kademlia.network import Server +import socket +import zstandard as zstd +from typing import List, Dict, Optional, Set +import time +import logging +from dataclasses import dataclass +from contextlib import asynccontextmanager +from aiohttp import web +import threading + +@dataclass +class PeerState: + last_seen: float + failed_attempts: int = 0 + is_active: bool = True + +class MeshNetwork: + def __init__(self, base_port: int = 12345, max_retry_ports: int = 5): + self.base_port = base_port + self.max_retry_ports = max_retry_ports + self.port: Optional[int] = None + self.dht = Server() + self.peers: Dict[str, PeerState] = {} + self.message_buffer: List[Dict] = [] + self.is_running = False + self.known_messages: Set[str] = set() + self.retry_interval = 30 + self.peer_timeout = 300 + self.logger = logging.getLogger('MeshNetwork') + self._http_runner = None + + async def _start_http_server(self): + """Start minimal HTTP server for health checks""" + if self._http_runner: + return True + + app = web.Application() + app.router.add_get('/', lambda _: web.Response(text="Mesh network running")) + app.router.add_get('/health', lambda _: web.Response(text="OK")) + + self._http_runner = web.AppRunner(app) + try: + await self._http_runner.setup() + site = web.TCPSite(self._http_runner, '0.0.0.0', self.base_port) + await site.start() + self.logger.info(f"✅ HTTP health check server started on port {self.base_port}") + return True + except Exception as e: + self.logger.error(f"❌ Failed to start HTTP server: {str(e)}") + if self._http_runner: + await self._http_runner.cleanup() + self._http_runner = None + return False + + async def _verify_port_active(self, port: int, timeout: int = 5) -> bool: + """Wait for port to become active with shorter timeout""" + end_time = time.time() + timeout + while time.time() < end_time: + try: + # Use the DHT's bootstrap functionality to verify + await self.dht.bootstrap([('127.0.0.1', port)]) + self.logger.info(f"✅ Port {port} is active and responding") + return True + except Exception as e: + self.logger.debug(f"Port verification attempt failed: {str(e)}") + await asyncio.sleep(0.5) + self.logger.error(f"❌ Port {port} did not become active within {timeout} seconds") + return False + + async def _cleanup_port(self, port: int): + """Cleanup a port before trying to use it""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(('0.0.0.0', port)) + sock.close() + except Exception as e: + self.logger.debug(f"Port cleanup failed: {str(e)}") + await asyncio.sleep(1) + + async def start(self) -> None: + """Start mesh network with robust port handling""" + # First try to start HTTP server on base port + self.logger.info(f"🚀 Starting HTTP health check server on port {self.base_port}") + if not await self._start_http_server(): + self.logger.warning("⚠️ Could not start HTTP server on base port") + + # Try ports for DHT + for port_offset in range(self.max_retry_ports): + test_port = self.base_port + port_offset + 1 # Start from base_port + 1 + self.logger.info(f"🔍 Attempting to start DHT on port {test_port}") + + # Cleanup port before use + await self._cleanup_port(test_port) + + try: + await self.dht.listen(test_port) + self.port = test_port + self.is_running = True + + # Start maintenance tasks + asyncio.create_task(self._peer_maintenance()) + asyncio.create_task(self._handle_message_buffer()) + asyncio.create_task(self._heartbeat()) + + # Short wait before verification + await asyncio.sleep(1) + + # Verify DHT is listening + if await self._verify_port_active(test_port): + self.logger.info(f"✅ DHT started successfully on port {test_port}") + return + else: + raise RuntimeError(f"DHT failed to bind to port {test_port}") + + except Exception as e: + self.logger.error(f"❌ Failed to start on port {test_port}: {str(e)}") + if self.dht: + try: + await self.dht.stop() + except: + pass + self.is_running = False + continue + + raise RuntimeError(f"Could not find available port in range {self.base_port+1}-{self.base_port + self.max_retry_ports}") + + async def stop(self) -> None: + """Stop mesh network gracefully""" + self.logger.info("Stopping mesh network") + self.is_running = False + + # Stop HTTP server + if self._http_runner: + await self._http_runner.cleanup() + self._http_runner = None + + # Stop DHT + if self.dht: + await self.dht.stop() + + await asyncio.sleep(1) # Allow tasks to complete + self.logger.info("Mesh network stopped") + + async def broadcast_message(self, message: str) -> None: + """Broadcast message to all peers with deduplication""" + msg_hash = hash(message) + if msg_hash in self.known_messages: + return + + self.known_messages.add(msg_hash) + compressed = self._compress_message(message) + + failed_peers = [] + for peer, state in self.peers.items(): + if not state.is_active: + continue + + try: + async with self._peer_connection(peer) as conn: + await self._send_message(conn, compressed) + except Exception as e: + self.logger.warning(f"Failed to send to {peer}: {str(e)}") + state.failed_attempts += 1 + if state.failed_attempts >= 3: + state.is_active = False + failed_peers.append((peer, compressed)) + + # Add failed deliveries to buffer + for peer, msg in failed_peers: + self.message_buffer.append({ + "peer": peer, + "message": msg, + "attempts": 0, + "timestamp": time.time() + }) + + @asynccontextmanager + async def _peer_connection(self, peer: str): + """Context manager for peer connections with timeout""" + reader, writer = await asyncio.wait_for( + asyncio.open_connection(peer, self.port), + timeout=10 + ) + try: + yield (reader, writer) + finally: + writer.close() + await writer.wait_closed() + + async def _send_message(self, conn, data: bytes) -> None: + """Send message with length prefix""" + reader, writer = conn + # Send length prefix + writer.write(len(data).to_bytes(4, 'big')) + writer.write(data) + await writer.drain() + + async def _peer_maintenance(self) -> None: + """Maintain peer list and handle failures""" + while self.is_running: + current_time = time.time() + # Remove stale peers + self.peers = { + peer: state + for peer, state in self.peers.items() + if current_time - state.last_seen < self.peer_timeout + } + + # Reset failed attempts periodically + for state in self.peers.values(): + if not state.is_active and state.failed_attempts > 0: + state.failed_attempts = max(0, state.failed_attempts - 1) + if state.failed_attempts == 0: + state.is_active = True + + await asyncio.sleep(60) + + async def _handle_message_buffer(self) -> None: + """Process buffered messages with exponential backoff""" + while self.is_running: + current_time = time.time() + for msg in self.message_buffer[:]: # Copy to allow modification + if msg["attempts"] < 5: # Try 5 times with increasing delays + try: + await self._send_to_peer(msg["peer"], msg["message"]) + self.message_buffer.remove(msg) + except Exception: + msg["attempts"] += 1 + # Exponential backoff + await asyncio.sleep(2 ** msg["attempts"]) + elif current_time - msg["timestamp"] > 3600: # Remove after 1 hour + self.message_buffer.remove(msg) + + await asyncio.sleep(self.retry_interval) + + async def _heartbeat(self) -> None: + """Send periodic heartbeats to peers""" + while self.is_running: + for peer, state in self.peers.items(): + if not state.is_active: + continue + + try: + async with self._peer_connection(peer) as conn: + await self._send_message(conn, b"PING") + state.last_seen = time.time() + state.failed_attempts = 0 + except Exception: + state.failed_attempts += 1 + if state.failed_attempts >= 3: + state.is_active = False + + await asyncio.sleep(30) # Heartbeat every 30 seconds + + def _compress_message(self, message: str) -> bytes: + """Compress message using zstd with error handling""" + try: + compressor = zstd.ZstdCompressor(level=3) # Balanced compression + return compressor.compress(message.encode()) + except Exception as e: + self.logger.error(f"Compression failed: {str(e)}") + # Fallback to uncompressed + return message.encode() + + def _decompress_message(self, data: bytes) -> str: + """Decompress message with error handling""" + try: + decompressor = zstd.ZstdDecompressor() + return decompressor.decompress(data).decode() + except Exception as e: + self.logger.error(f"Decompression failed: {str(e)}") + # Try to return as-is if decompression fails + return data.decode(errors='replace') + + async def _send_to_peer(self, peer: str, data: bytes): + async with self._peer_connection(peer) as conn: + await self._send_message(conn, data) + self.logger.info(f"Message sent to {peer}") #Added logging + + async def _verify_port_available(self, port: int) -> bool: + """Verify if a port is available""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind(('0.0.0.0', port)) + sock.close() + return True + except: + return False + + async def _wait_for_port_active(self, port: int, timeout: int = 30) -> bool: + """Wait for port to become active""" + end_time = time.time() + timeout + while time.time() < end_time: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('127.0.0.1', port)) + sock.close() + return True + except: + await asyncio.sleep(1) + return False diff --git a/network/tor_manager.py b/network/tor_manager.py new file mode 100644 index 0000000..8f91b68 --- /dev/null +++ b/network/tor_manager.py @@ -0,0 +1,196 @@ +import os +import subprocess +import asyncio +import socket +import socks +from typing import Optional +import shutil +import platform +import sys +import stat + +class TorManager: + def __init__(self): + self.tor_process: Optional[subprocess.Popen] = None + self.onion_address: Optional[str] = None + self.socks_port = 9052 + self.control_port = 9053 + self.base_dir = self._get_base_dir() + + def _get_base_dir(self) -> str: + """Get the portable base directory for Tor files""" + if getattr(sys, 'frozen', False): + # If running as compiled executable + base_path = os.path.dirname(sys.executable) + else: + # If running from source + base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + # Use a hidden directory in the user's home + return os.path.join(os.path.expanduser("~"), ".tormesh") + + def _secure_dir_permissions(self, path: str): + """Set secure permissions on directory""" + os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) # 700 + + def _get_tor_path(self) -> str: + """Get platform-specific Tor binary path with fallbacks""" + if platform.system() == 'Windows': + tor_names = ['tor.exe'] + else: + tor_names = ['tor'] + + # Search in system paths first + search_paths = [ + "/usr/bin", + "/usr/local/bin", + "/opt/homebrew/bin", # For MacOS + os.path.join(self.base_dir, "bin") + ] + + # Add PATH directories + if os.environ.get('PATH'): + search_paths.extend(os.environ['PATH'].split(os.pathsep)) + + for path in search_paths: + for name in tor_names: + tor_path = os.path.join(path, name) + if os.path.exists(tor_path) and os.access(tor_path, os.X_OK): + return tor_path + + raise FileNotFoundError( + "Tor binary not found. Please ensure Tor is installed." + ) + + async def start(self): + """Start Tor with robust error handling and retries""" + # Create base directory with secure permissions + os.makedirs(self.base_dir, exist_ok=True) + self._secure_dir_permissions(self.base_dir) + + # Create necessary subdirectories + data_dir = os.path.join(self.base_dir, "data") + hidden_service_dir = os.path.join(self.base_dir, "hidden_service") + + for directory in [data_dir, hidden_service_dir]: + os.makedirs(directory, exist_ok=True) + self._secure_dir_permissions(directory) + + # Create torrc configuration + torrc_path = os.path.join(self.base_dir, "torrc") + with open(torrc_path, "w") as f: + f.write(f""" +SocksPort 127.0.0.1:{self.socks_port} +ControlPort 127.0.0.1:{self.control_port} +DataDirectory {data_dir} +HiddenServiceDir {hidden_service_dir} +HiddenServicePort 12345 127.0.0.1:12345 +# Improve anonymity +UseEntryGuards 1 +NumEntryGuards 4 +# Improve resilience +CircuitBuildTimeout 60 +LearnCircuitBuildTimeout 1 +MaxCircuitDirtiness 600 +NewCircuitPeriod 300 +""") + self._secure_dir_permissions(os.path.dirname(torrc_path)) + + # Start Tor process with retries + max_retries = 3 + retry_delay = 2 + + for attempt in range(max_retries): + try: + tor_path = self._get_tor_path() + print(f"Starting Tor from: {tor_path}") + + self.tor_process = subprocess.Popen( + [tor_path, "-f", torrc_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + # Wait for Tor to start and verify it's running + if await self._wait_for_tor(): + print(f"Tor started successfully on attempt {attempt + 1}") + return + + except Exception as e: + print(f"Tor start attempt {attempt + 1} failed: {str(e)}") + if self.tor_process: + self.tor_process.terminate() + await asyncio.sleep(1) + + if attempt < max_retries - 1: + await asyncio.sleep(retry_delay) + else: + raise Exception(f"Failed to start Tor after {max_retries} attempts") + + async def _wait_for_tor(self) -> bool: + """Wait for Tor to start with improved verification""" + hostname_file = os.path.join( + self.base_dir, "hidden_service", "hostname" + ) + + for _ in range(30): # Wait up to 30 seconds + # Check if process is still running + if self.tor_process and self.tor_process.poll() is not None: + if self.tor_process.stderr: + stderr = self.tor_process.stderr.read().decode() + raise Exception(f"Tor process died: {stderr}") + raise Exception("Tor process died unexpectedly") + + # Check for hostname file + if os.path.exists(hostname_file): + try: + with open(hostname_file, "r") as f: + self.onion_address = f.read().strip() + + # Verify SOCKS port is listening + sock = socket.socket() + try: + sock.connect(("127.0.0.1", self.socks_port)) + sock.close() + return True + except: + pass + except: + pass + + await asyncio.sleep(1) + + return False + + async def stop(self): + """Stop Tor with graceful shutdown""" + if self.tor_process: + try: + self.tor_process.terminate() + try: + await asyncio.wait_for( + asyncio.create_task(self.tor_process.wait()), + timeout=5 + ) + except asyncio.TimeoutError: + self.tor_process.kill() + + # Clear sensitive data + if self.onion_address: + self.onion_address = None + + # Clean up temporary files securely + if os.path.exists(self.base_dir): + try: + # Securely delete sensitive files + for root, dirs, files in os.walk(self.base_dir): + for f in files: + path = os.path.join(root, f) + with open(path, 'wb') as fd: + fd.write(os.urandom(os.path.getsize(path))) + # Remove directory tree + shutil.rmtree(self.base_dir) + except: + pass + except: + pass # Ensure cleanup doesn't raise errors diff --git a/security/__init__.py b/security/__init__.py new file mode 100644 index 0000000..8b5131e --- /dev/null +++ b/security/__init__.py @@ -0,0 +1,9 @@ +""" +Security package for P2P/Mesh chat application. +Handles cryptography and secure memory operations. +""" + +from .crypto import CryptoManager +from .memory import SecureMemory + +__all__ = ['CryptoManager', 'SecureMemory'] diff --git a/security/__pycache__/__init__.cpython-313.pyc b/security/__pycache__/__init__.cpython-313.pyc Binary files differnew file mode 100644 index 0000000..45ee6b0 --- /dev/null +++ b/security/__pycache__/__init__.cpython-313.pyc diff --git a/security/__pycache__/crypto.cpython-313.pyc b/security/__pycache__/crypto.cpython-313.pyc Binary files differnew file mode 100644 index 0000000..3cba246 --- /dev/null +++ b/security/__pycache__/crypto.cpython-313.pyc diff --git a/security/__pycache__/memory.cpython-313.pyc b/security/__pycache__/memory.cpython-313.pyc Binary files differnew file mode 100644 index 0000000..ec12fd7 --- /dev/null +++ b/security/__pycache__/memory.cpython-313.pyc diff --git a/security/crypto.py b/security/crypto.py new file mode 100644 index 0000000..f961a21 --- /dev/null +++ b/security/crypto.py @@ -0,0 +1,37 @@ +from pyspx.shake_256f import generate_keypair, sign, verify +import hashlib +from typing import Tuple, Optional +import os + +class CryptoManager: + def __init__(self): + self.signing_key: Optional[bytes] = None + self.verify_key: Optional[bytes] = None + + def generate_keys(self) -> Tuple[bytes, bytes]: + """Generate new signing keypair""" + self.signing_key, self.verify_key = generate_keypair() + return self.signing_key, self.verify_key + + def sign_message(self, message: bytes) -> bytes: + """Sign a message using the signing key""" + if not self.signing_key: + raise ValueError("No signing key available") + return sign(message, self.signing_key) + + def verify_signature(self, message: bytes, signature: bytes, + public_key: bytes) -> bool: + """Verify a message signature""" + try: + verify(message, signature, public_key) + return True + except: + return False + + def hash_data(self, data: bytes) -> str: + """Create SHA-256 hash of data""" + return hashlib.sha256(data).hexdigest() + + def generate_nonce(self) -> bytes: + """Generate a random nonce""" + return os.urandom(32) diff --git a/security/memory.py b/security/memory.py new file mode 100644 index 0000000..6a6e96e --- /dev/null +++ b/security/memory.py @@ -0,0 +1,38 @@ + +import nacl.bindings +import ctypes +import platform + +class SecureMemory: + def __init__(self): + self.protected_blocks = [] + + def protect_memory(self, data: bytes) -> bytearray: + """Protect memory block from being swapped to disk""" + protected = bytearray(data) + self.protected_blocks.append(protected) + + if platform.system() != 'Windows': + try: + # Lock memory to prevent swapping + libc = ctypes.CDLL('libc.so.6') + libc.mlock( + ctypes.c_void_p(protected.__array_interface__['data'][0]), + len(protected) + ) + except: + pass + + return protected + + def secure_wipe(self, data: bytearray): + """Securely wipe memory block""" + if data in self.protected_blocks: + self.protected_blocks.remove(data) + + nacl.bindings.sodium_memzero(data) + + def wipe_all(self): + """Wipe all protected memory blocks""" + for block in self.protected_blocks[:]: + self.secure_wipe(block) diff --git a/test_health.py b/test_health.py new file mode 100644 index 0000000..23ac584 --- /dev/null +++ b/test_health.py @@ -0,0 +1,61 @@ + +# Secure P2P Chat Application + +A secure P2P chat application with advanced mesh networking capabilities, designed for resilient and private communication. + +## Key Features +- Mesh Networking Protocol +- Tor Integration +- Distributed Hash Table (DHT) +- Secure Communication Channels +- Health Monitoring Services + +## Project Structure +``` +. +├── network/ +│ ├── __init__.py +│ ├── mesh.py +│ └── tor_manager.py +├── security/ +│ ├── __init__.py +│ ├── crypto.py +│ └── memory.py +├── ui/ +│ ├── __init__.py +│ └── chat_window.py +├── mesh_chat.py +├── test_health.py +└── libraries.html +``` + +## Required Dependencies +```python +# Install using pip +aiohttp +kademlia +pynacl +pysocks +pyspx +zstandard +``` + +## Running the Application +1. Install dependencies: +```bash +pip install aiohttp kademlia pynacl pysocks pyspx zstandard +``` + +2. Start the application: +```bash +python mesh_chat.py +``` + +3. Test health check: +```bash +python test_health.py +``` + +## Testing +- HTTP Health Check: Visit http://localhost:12345 +- Health Status: Visit http://localhost:12345/health diff --git a/tor/torrc b/tor/torrc new file mode 100644 index 0000000..26377be --- /dev/null +++ b/tor/torrc @@ -0,0 +1,5 @@ +SocksPort 127.0.0.1:9052 +ControlPort 127.0.0.1:9053 +DataDirectory /home/runner/workspace/tor/data +HiddenServiceDir /home/runner/workspace/tor/hidden_service +HiddenServicePort 12345 127.0.0.1:12345 diff --git a/tor_data/torrc b/tor_data/torrc new file mode 100644 index 0000000..46c9498 --- /dev/null +++ b/tor_data/torrc @@ -0,0 +1,13 @@ +SocksPort 127.0.0.1:9052 +ControlPort 127.0.0.1:9053 +DataDirectory /home/runner/workspace/tor_data/data +HiddenServiceDir /home/runner/workspace/tor_data/hidden_service +HiddenServicePort 12345 127.0.0.1:12345 +# Improve anonymity +UseEntryGuards 1 +NumEntryGuards 4 +# Improve resilience +CircuitBuildTimeout 60 +LearnCircuitBuildTimeout 1 +MaxCircuitDirtiness 600 +NewCircuitPeriod 300 diff --git a/ui/__init__.py b/ui/__init__.py new file mode 100644 index 0000000..89a4640 --- /dev/null +++ b/ui/__init__.py @@ -0,0 +1,8 @@ +""" +UI package for P2P/Mesh chat application. +Contains chat window and user interface components. +""" + +from .chat_window import ChatWindow + +__all__ = ['ChatWindow'] diff --git a/ui/__pycache__/__init__.cpython-313.pyc b/ui/__pycache__/__init__.cpython-313.pyc Binary files differnew file mode 100644 index 0000000..dda4418 --- /dev/null +++ b/ui/__pycache__/__init__.cpython-313.pyc diff --git a/ui/__pycache__/chat_window.cpython-313.pyc b/ui/__pycache__/chat_window.cpython-313.pyc Binary files differnew file mode 100644 index 0000000..49785e2 --- /dev/null +++ b/ui/__pycache__/chat_window.cpython-313.pyc diff --git a/ui/chat_window.py b/ui/chat_window.py new file mode 100644 index 0000000..c341cf1 --- /dev/null +++ b/ui/chat_window.py @@ -0,0 +1,159 @@ +import tkinter as tk +from tkinter import scrolledtext, messagebox +import asyncio +from typing import Callable, Optional +from network.mesh import MeshNetwork +from network.tor_manager import TorManager +from security.crypto import CryptoManager +from security.memory import SecureMemory + +class ChatWindow: + def __init__( + self, + mesh_network: MeshNetwork, + tor_manager: TorManager, + crypto: CryptoManager, + secure_memory: SecureMemory + ): + self.mesh_network = mesh_network + self.tor_manager = tor_manager + self.crypto = crypto + self.secure_memory = secure_memory + self.root: Optional[tk.Tk] = None + self.is_running = True + + async def run(self): + self.root = tk.Tk() + self.root.title("Secure Mesh Chat") + self.root.geometry("600x800") + + # Status frame + status_frame = tk.Frame(self.root) + status_frame.pack(fill=tk.X, padx=5, pady=5) + + # Onion address + onion_label = tk.Label( + status_frame, + text=f"Onion: {self.tor_manager.onion_address}", + fg="green" + ) + onion_label.pack(side=tk.LEFT) + + # Peer count + peer_label = tk.Label( + status_frame, + text=f"Peers: {len(self.mesh_network.peers)}" + ) + peer_label.pack(side=tk.RIGHT) + + # Chat area + self.chat_area = scrolledtext.ScrolledText( + self.root, + wrap=tk.WORD, + height=30 + ) + self.chat_area.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + # Input area + input_frame = tk.Frame(self.root) + input_frame.pack(fill=tk.X, padx=5, pady=5) + + self.msg_entry = tk.Entry(input_frame) + self.msg_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) + + send_btn = tk.Button( + input_frame, + text="Send", + command=self._send_message + ) + send_btn.pack(side=tk.RIGHT, padx=5) + + # Control buttons + control_frame = tk.Frame(self.root) + control_frame.pack(fill=tk.X, padx=5, pady=5) + + tk.Button( + control_frame, + text="Clear Chat", + command=self._clear_chat + ).pack(side=tk.LEFT) + + tk.Button( + control_frame, + text="Network Status", + command=self._show_status + ).pack(side=tk.RIGHT) + + # Set up close handler + self.root.protocol("WM_DELETE_WINDOW", self._on_close) + + # Start message receiver + asyncio.create_task(self._receive_messages()) + + # Start periodic UI updates + asyncio.create_task(self._update_ui()) + + while self.is_running: + try: + self.root.update() + await asyncio.sleep(0.1) + except tk.TclError: # Window was closed + break + + def _send_message(self): + msg = self.msg_entry.get() + if msg: + asyncio.create_task(self.mesh_network.broadcast_message(msg)) + self.chat_area.insert(tk.END, f"You: {msg}\n") + self.msg_entry.delete(0, tk.END) + + def _clear_chat(self): + if messagebox.askyesno("Confirm", "Clear all messages?"): + # Get current content before clearing + content = self.chat_area.get("1.0", tk.END).encode() + + # Clear the chat area first + self.chat_area.delete("1.0", tk.END) + + # Then securely wipe the content + protected = self.secure_memory.protect_memory(content) + self.secure_memory.secure_wipe(protected) + + def _show_status(self): + status = f""" +Network Status: +-------------- +Tor: {'Running' if self.tor_manager.tor_process else 'Stopped'} +Onion: {self.tor_manager.onion_address} +Active Peers: {len(self.mesh_network.peers)} +Buffered Messages: {len(self.mesh_network.message_buffer)} +""" + messagebox.showinfo("Status", status) + + def _on_close(self): + if messagebox.askyesno("Quit", "Are you sure you want to quit?"): + self.is_running = False # Stop the main loop + if self.root: + self.root.quit() # Stop Tkinter + self.root.destroy() # Destroy the window + + async def _receive_messages(self): + while self.is_running: + # Process received messages + await asyncio.sleep(0.1) + + async def _update_ui(self): + while self.is_running: + if not self.root: + break + + try: + # Update peer count + peer_count = len(self.mesh_network.peers) + self.root.nametowidget(".!frame.!label2").configure( + text=f"Peers: {peer_count}" + ) + except tk.TclError: # Window was closed + break + + await asyncio.sleep(1) |
