Better CLI & auto mode, enhancements
All checks were successful
/ mirror (push) Successful in 5s

This commit is contained in:
stcb 2025-04-05 10:19:39 +03:00
parent 0b52d602ef
commit 71b9cc787b
3 changed files with 819 additions and 62 deletions

View File

@ -0,0 +1,430 @@
import time
import threading
import queue
from typing import Optional, Dict, Any, List, Callable, Tuple
# ANSI colors for logging
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
RESET = "\033[0m"
class AutoModeConfig:
"""Configuration parameters for the automatic mode behavior."""
def __init__(self):
# Ping behavior
self.ping_response_accept = True # Whether to accept incoming pings
self.ping_auto_initiate = False # Whether to initiate pings when connected
self.ping_retry_count = 3 # Number of ping retries
self.ping_retry_delay = 5.0 # Seconds between ping retries
self.ping_timeout = 10.0 # Seconds to wait for ping response
self.preferred_cipher = 0 # 0=AES-GCM, 1=ChaCha20-Poly1305
# Handshake behavior
self.handshake_retry_count = 3 # Number of handshake retries
self.handshake_retry_delay = 5.0 # Seconds between handshake retries
self.handshake_timeout = 10.0 # Seconds to wait for handshake
# Messaging behavior
self.auto_message_enabled = False # Whether to auto-send messages
self.message_interval = 10.0 # Seconds between auto messages
self.message_content = "Hello, secure world!" # Default message
# General behavior
self.active_mode = False # If true, initiates protocol instead of waiting
class AutoMode:
"""
Manages automated behavior for the Icing protocol.
Handles automatic progression through the protocol stages:
1. Connection setup
2. Ping/discovery
3. Key exchange
4. Encrypted communication
"""
def __init__(self, protocol_interface):
"""
Initialize the AutoMode manager.
Args:
protocol_interface: An object implementing the required protocol methods
"""
self.protocol = protocol_interface
self.config = AutoModeConfig()
self.active = False
self.state = "idle"
# Message queue for automated sending
self.message_queue = queue.Queue()
# Tracking variables
self.ping_attempts = 0
self.handshake_attempts = 0
self.last_action_time = 0
self.timer_tasks = [] # List of active timer tasks (for cleanup)
def start(self):
"""Start the automatic mode."""
if self.active:
return
self.active = True
self.state = "idle"
self.ping_attempts = 0
self.handshake_attempts = 0
self.last_action_time = time.time()
self._log_info("Automatic mode started")
# Start in active mode if configured
if self.config.active_mode and self.protocol.connections:
self._start_ping_sequence()
def stop(self):
"""Stop the automatic mode and clean up any pending tasks."""
if not self.active:
return
# Cancel any pending timers
for timer in self.timer_tasks:
if timer.is_alive():
timer.cancel()
self.timer_tasks = []
self.active = False
self.state = "idle"
self._log_info("Automatic mode stopped")
def handle_connection_established(self):
"""Called when a new connection is established."""
if not self.active:
return
self._log_info("Connection established")
# If in active mode, start pinging
if self.config.active_mode:
self._start_ping_sequence()
def handle_ping_received(self, index: int):
"""
Handle a received ping request.
Args:
index: Index of the ping request in the protocol's inbound message queue
"""
if not self.active or not self._is_valid_message_index(index):
return
self._log_info(f"Ping request received (index={index})")
# Automatically respond to ping if configured to accept
if self.config.ping_response_accept:
self._log_info(f"Auto-responding to ping with accept={self.config.ping_response_accept}")
try:
# Schedule the response with a small delay to simulate real behavior
timer = threading.Timer(0.5, self._respond_to_ping, args=[index])
timer.daemon = True
timer.start()
self.timer_tasks.append(timer)
except Exception as e:
self._log_error(f"Failed to auto-respond to ping: {e}")
def handle_ping_response_received(self, accepted: bool):
"""
Handle a received ping response.
Args:
accepted: Whether the ping was accepted
"""
if not self.active:
return
self.ping_attempts = 0 # Reset ping attempts counter
if accepted:
self._log_info("Ping accepted! Proceeding with handshake")
# Send handshake if not already done
if self.state != "handshake_sent":
self._ensure_ephemeral_keys()
self._start_handshake_sequence()
else:
self._log_info("Ping rejected by peer. Stopping auto-protocol sequence.")
self.state = "idle"
def handle_handshake_received(self, index: int):
"""
Handle a received handshake.
Args:
index: Index of the handshake in the protocol's inbound message queue
"""
if not self.active or not self._is_valid_message_index(index):
return
self._log_info(f"Handshake received (index={index})")
try:
# Ensure we have ephemeral keys
self._ensure_ephemeral_keys()
# Process the handshake (compute ECDH)
self.protocol.generate_ecdhe(index)
# Derive HKDF key
self.protocol.derive_hkdf()
# If we haven't sent our handshake yet, send it
if self.state != "handshake_sent":
timer = threading.Timer(0.5, self.protocol.send_handshake)
timer.daemon = True
timer.start()
self.timer_tasks.append(timer)
self.state = "handshake_sent"
else:
self.state = "key_exchange_complete"
# Start sending queued messages if auto messaging is enabled
if self.config.auto_message_enabled:
self._start_message_sequence()
except Exception as e:
self._log_error(f"Failed to process handshake: {e}")
def handle_encrypted_received(self, index: int):
"""
Handle a received encrypted message.
Args:
index: Index of the encrypted message in the protocol's inbound message queue
"""
if not self.active or not self._is_valid_message_index(index):
return
# Try to decrypt automatically
try:
plaintext = self.protocol.decrypt_received_message(index)
self._log_info(f"Auto-decrypted message: {plaintext}")
except Exception as e:
self._log_error(f"Failed to auto-decrypt message: {e}")
def queue_message(self, message: str):
"""
Add a message to the auto-send queue.
Args:
message: Message text to send
"""
self.message_queue.put(message)
self._log_info(f"Message queued for sending: {message}")
# If we're in the right state, start sending messages
if self.active and self.state == "key_exchange_complete" and self.config.auto_message_enabled:
self._process_message_queue()
def _start_ping_sequence(self):
"""Start the ping sequence to discover the peer."""
if self.ping_attempts >= self.config.ping_retry_count:
self._log_warning(f"Maximum ping attempts ({self.config.ping_retry_count}) reached")
self.state = "idle"
return
self.state = "pinging"
self.ping_attempts += 1
self._log_info(f"Sending ping request (attempt {self.ping_attempts}/{self.config.ping_retry_count})")
try:
self.protocol.send_ping_request(self.config.preferred_cipher)
self.last_action_time = time.time()
# Schedule next ping attempt if needed
timer = threading.Timer(
self.config.ping_retry_delay,
self._check_ping_response
)
timer.daemon = True
timer.start()
self.timer_tasks.append(timer)
except Exception as e:
self._log_error(f"Failed to send ping: {e}")
def _check_ping_response(self):
"""Check if we got a ping response, retry if not."""
if not self.active or self.state != "pinging":
return
# If we've waited long enough for a response, retry
if time.time() - self.last_action_time >= self.config.ping_timeout:
self._log_warning("No ping response received, retrying")
self._start_ping_sequence()
def _respond_to_ping(self, index: int):
"""
Respond to a ping request.
Args:
index: Index of the ping request in the inbound messages
"""
if not self.active or not self._is_valid_message_index(index):
return
try:
answer = 1 if self.config.ping_response_accept else 0
self.protocol.respond_to_ping(index, answer)
if answer == 1:
# If we accepted, we should expect a handshake
self.state = "accepted_ping"
self._ensure_ephemeral_keys()
# Set a timer to send our handshake if we don't receive one
timer = threading.Timer(
self.config.handshake_timeout,
self._check_handshake_received
)
timer.daemon = True
timer.start()
self.timer_tasks.append(timer)
self.last_action_time = time.time()
except Exception as e:
self._log_error(f"Failed to respond to ping: {e}")
def _check_handshake_received(self):
"""Check if we've received a handshake after accepting a ping."""
if not self.active or self.state != "accepted_ping":
return
# If we've waited long enough and haven't received a handshake, initiate one
if time.time() - self.last_action_time >= self.config.handshake_timeout:
self._log_warning("No handshake received after accepting ping, initiating handshake")
self._start_handshake_sequence()
def _start_handshake_sequence(self):
"""Start the handshake sequence."""
if self.handshake_attempts >= self.config.handshake_retry_count:
self._log_warning(f"Maximum handshake attempts ({self.config.handshake_retry_count}) reached")
self.state = "idle"
return
self.state = "handshake_sent"
self.handshake_attempts += 1
self._log_info(f"Sending handshake (attempt {self.handshake_attempts}/{self.config.handshake_retry_count})")
try:
self.protocol.send_handshake()
self.last_action_time = time.time()
# Schedule handshake retry check
timer = threading.Timer(
self.config.handshake_retry_delay,
self._check_handshake_response
)
timer.daemon = True
timer.start()
self.timer_tasks.append(timer)
except Exception as e:
self._log_error(f"Failed to send handshake: {e}")
def _check_handshake_response(self):
"""Check if we've completed the key exchange, retry handshake if not."""
if not self.active or self.state != "handshake_sent":
return
# If we've waited long enough for a response, retry
if time.time() - self.last_action_time >= self.config.handshake_timeout:
self._log_warning("No handshake response received, retrying")
self._start_handshake_sequence()
def _start_message_sequence(self):
"""Start the automated message sending sequence."""
if not self.config.auto_message_enabled:
return
self._log_info("Starting automated message sequence")
# Add the default message if queue is empty
if self.message_queue.empty():
self.message_queue.put(self.config.message_content)
# Start processing the queue
self._process_message_queue()
def _process_message_queue(self):
"""Process messages in the queue and send them."""
if not self.active or self.state != "key_exchange_complete" or not self.config.auto_message_enabled:
return
if not self.message_queue.empty():
message = self.message_queue.get()
self._log_info(f"Sending queued message: {message}")
try:
self.protocol.send_encrypted_message(message)
# Schedule next message send
timer = threading.Timer(
self.config.message_interval,
self._process_message_queue
)
timer.daemon = True
timer.start()
self.timer_tasks.append(timer)
except Exception as e:
self._log_error(f"Failed to send queued message: {e}")
# Put the message back in the queue
self.message_queue.put(message)
def _ensure_ephemeral_keys(self):
"""Ensure ephemeral keys are generated if needed."""
if not hasattr(self.protocol, 'ephemeral_pubkey') or self.protocol.ephemeral_pubkey is None:
self._log_info("Generating ephemeral keys")
self.protocol.generate_ephemeral_keys()
def _is_valid_message_index(self, index: int) -> bool:
"""
Check if a message index is valid in the protocol's inbound_messages queue.
Args:
index: The index to check
Returns:
bool: True if the index is valid, False otherwise
"""
if not hasattr(self.protocol, 'inbound_messages'):
self._log_error("Protocol has no inbound_messages attribute")
return False
if index < 0 or index >= len(self.protocol.inbound_messages):
self._log_error(f"Invalid message index: {index}")
return False
return True
# Helper methods for logging
def _log_info(self, message: str):
print(f"{BLUE}[AUTO]{RESET} {message}")
if hasattr(self, 'verbose_logging') and self.verbose_logging:
state_info = f"(state={self.state})"
if 'pinging' in self.state and hasattr(self, 'ping_attempts'):
state_info += f", attempts={self.ping_attempts}/{self.config.ping_retry_count}"
elif 'handshake' in self.state and hasattr(self, 'handshake_attempts'):
state_info += f", attempts={self.handshake_attempts}/{self.config.handshake_retry_count}"
print(f"{BLUE}[AUTO-DETAIL]{RESET} {state_info}")
def _log_warning(self, message: str):
print(f"{YELLOW}[AUTO-WARN]{RESET} {message}")
if hasattr(self, 'verbose_logging') and self.verbose_logging:
timer_info = f"Active timers: {len(self.timer_tasks)}"
print(f"{YELLOW}[AUTO-WARN-DETAIL]{RESET} {timer_info}")
def _log_error(self, message: str):
print(f"{RED}[AUTO-ERROR]{RESET} {message}")
if hasattr(self, 'verbose_logging') and self.verbose_logging:
print(f"{RED}[AUTO-ERROR-DETAIL]{RESET} Current state: {self.state}, Active: {self.active}")

View File

@ -1,12 +1,53 @@
import sys
import argparse
import shlex
from protocol import IcingProtocol
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
MAGENTA = "\033[95m"
CYAN = "\033[96m"
RESET = "\033[0m"
def print_help():
"""Display all available commands."""
print(f"\n{YELLOW}=== Available Commands ==={RESET}")
print(f"\n{CYAN}Basic Protocol Commands:{RESET}")
print(" help - Show this help message")
print(" peer_id <hex_pubkey> - Set peer identity public key")
print(" connect <port> - Connect to a peer at the specified port")
print(" show_state - Display current protocol state")
print(" exit - Exit the program")
print(f"\n{CYAN}Manual Protocol Operation:{RESET}")
print(" generate_ephemeral_keys - Generate ephemeral ECDH keys")
print(" send_ping [cipher] - Send PING request (cipher: 0=AES-GCM, 1=ChaCha20-Poly1305, default: 0)")
print(" respond_ping <index> <0|1> - Respond to a PING (0=reject, 1=accept)")
print(" send_handshake - Send handshake with ephemeral keys")
print(" generate_ecdhe <index> - Process handshake at specified index")
print(" derive_hkdf - Derive encryption key using HKDF")
print(" send_encrypted <plaintext> - Encrypt and send a message")
print(" decrypt <index> - Decrypt received message at index")
print(f"\n{CYAN}Automatic Mode Commands:{RESET}")
print(" auto start - Start automatic mode")
print(" auto stop - Stop automatic mode")
print(" auto status - Show current auto mode status and configuration")
print(" auto config <param> <value> - Configure auto mode parameters")
print(" auto config list - Show all configurable parameters")
print(" auto message <text> - Queue message for automatic sending")
print(" auto passive - Configure as passive peer (responds to pings but doesn't initiate)")
print(" auto active - Configure as active peer (initiates protocol)")
print(" auto log - Toggle detailed logging for auto mode")
print(f"\n{CYAN}Debugging Commands:{RESET}")
print(" debug_message <index> - Display detailed information about a message in the queue")
print(f"\n{CYAN}Legacy Commands:{RESET}")
print(" auto_responder <on|off> - Enable/disable legacy auto responder (deprecated)")
def main():
protocol = IcingProtocol()
@ -16,54 +57,27 @@ def main():
print("======================================\n" + RESET)
print(f"Listening on port: {protocol.local_port}")
print(f"Your identity public key (hex): {protocol.identity_pubkey.hex()}")
print("\nAvailable commands:")
print(" help - Show this help message")
print(" peer_id <hex_pubkey> - Set peer identity public key")
print(" connect <port> - Connect to a peer at the specified port")
print(" generate_ephemeral_keys - Generate ephemeral ECDH keys")
print(" send_ping [cipher] - Send PING request (cipher: 0=AES-GCM, 1=ChaCha20-Poly1305, default: 0)")
print(" respond_ping <index> <0|1> - Respond to a PING (0=reject, 1=accept)")
print(" send_handshake - Send handshake with ephemeral keys")
print(" generate_ecdhe <index> - Process handshake at specified index")
print(" derive_hkdf - Derive encryption key using HKDF")
print(" send_encrypted <plaintext> - Encrypt and send a message")
print(" decrypt <index> - Decrypt received message at index")
print(" auto_responder <on|off> - Enable/disable automatic responses")
print(" show_state - Display current protocol state")
print(" exit - Exit the program\n")
print_help()
while True:
try:
line = input("Cmd> ").strip()
line = input(f"{MAGENTA}Cmd>{RESET} ").strip()
except EOFError:
break
if not line:
continue
parts = line.split()
parts = shlex.split(line) # Handle quoted arguments properly
cmd = parts[0].lower()
try:
# Basic commands
if cmd == "exit":
protocol.stop()
break
elif cmd == "help":
print("\nAvailable commands:")
print(" help - Show this help message")
print(" peer_id <hex_pubkey> - Set peer identity public key")
print(" connect <port> - Connect to a peer at the specified port")
print(" generate_ephemeral_keys - Generate ephemeral ECDH keys")
print(" send_ping [cipher] - Send PING request (cipher: 0=AES-GCM, 1=ChaCha20-Poly1305, default: 0)")
print(" respond_ping <index> <0|1> - Respond to a PING (0=reject, 1=accept)")
print(" send_handshake - Send handshake with ephemeral keys")
print(" generate_ecdhe <index> - Process handshake at specified index")
print(" derive_hkdf - Derive encryption key using HKDF")
print(" send_encrypted <plaintext> - Encrypt and send a message")
print(" decrypt <index> - Decrypt received message at index")
print(" auto_responder <on|off> - Enable/disable automatic responses")
print(" show_state - Display current protocol state")
print(" exit - Exit the program")
print_help()
elif cmd == "show_state":
protocol.show_state()
@ -88,7 +102,8 @@ def main():
print(f"{RED}[ERROR]{RESET} Invalid port number.")
except Exception as e:
print(f"{RED}[ERROR]{RESET} Connection failed: {e}")
# Manual protocol operation
elif cmd == "generate_ephemeral_keys":
protocol.generate_ephemeral_keys()
@ -103,7 +118,7 @@ def main():
cipher = 0
except ValueError:
print(f"{YELLOW}[WARNING]{RESET} Invalid cipher code. Using AES-GCM (0).")
protocol.send_ping_request()
protocol.send_ping_request(cipher)
elif cmd == "send_handshake":
protocol.send_handshake()
@ -164,6 +179,127 @@ def main():
except Exception as e:
print(f"{RED}[ERROR]{RESET} Failed to decrypt message: {e}")
# Debugging commands
elif cmd == "debug_message":
if len(parts) != 2:
print(f"{RED}[ERROR]{RESET} Usage: debug_message <index>")
continue
try:
idx = int(parts[1])
protocol.debug_message(idx)
except ValueError:
print(f"{RED}[ERROR]{RESET} Index must be an integer.")
except Exception as e:
print(f"{RED}[ERROR]{RESET} Failed to debug message: {e}")
# Automatic mode commands
elif cmd == "auto":
if len(parts) < 2:
print(f"{RED}[ERROR]{RESET} Usage: auto <command> [options]")
print("Available commands: start, stop, status, config, message, passive, active")
continue
subcmd = parts[1].lower()
if subcmd == "start":
protocol.start_auto_mode()
print(f"{GREEN}[AUTO]{RESET} Automatic mode started")
elif subcmd == "stop":
protocol.stop_auto_mode()
print(f"{GREEN}[AUTO]{RESET} Automatic mode stopped")
elif subcmd == "status":
config = protocol.get_auto_mode_config()
print(f"{YELLOW}=== Auto Mode Status ==={RESET}")
print(f"Active: {protocol.auto_mode.active}")
print(f"State: {protocol.auto_mode.state}")
print(f"\n{YELLOW}--- Configuration ---{RESET}")
for key, value in vars(config).items():
print(f" {key}: {value}")
elif subcmd == "config":
if len(parts) < 3:
print(f"{RED}[ERROR]{RESET} Usage: auto config <param> <value> or auto config list")
continue
if parts[2].lower() == "list":
config = protocol.get_auto_mode_config()
print(f"{YELLOW}=== Auto Mode Configuration Parameters ==={RESET}")
for key, value in vars(config).items():
print(f" {key} ({type(value).__name__}): {value}")
continue
if len(parts) != 4:
print(f"{RED}[ERROR]{RESET} Usage: auto config <param> <value>")
continue
param = parts[2]
value_str = parts[3]
# Convert the string value to the appropriate type
config = protocol.get_auto_mode_config()
if not hasattr(config, param):
print(f"{RED}[ERROR]{RESET} Unknown parameter: {param}")
print("Use 'auto config list' to see all available parameters")
continue
current_value = getattr(config, param)
try:
if isinstance(current_value, bool):
if value_str.lower() in ("true", "yes", "on", "1"):
value = True
elif value_str.lower() in ("false", "no", "off", "0"):
value = False
else:
raise ValueError(f"Boolean value must be true/false/yes/no/on/off/1/0")
elif isinstance(current_value, int):
value = int(value_str)
elif isinstance(current_value, float):
value = float(value_str)
elif isinstance(current_value, str):
value = value_str
else:
value = value_str # Default to string
protocol.configure_auto_mode(**{param: value})
print(f"{GREEN}[AUTO]{RESET} Set {param} = {value}")
except ValueError as e:
print(f"{RED}[ERROR]{RESET} Invalid value for {param}: {e}")
elif subcmd == "message":
if len(parts) < 3:
print(f"{RED}[ERROR]{RESET} Usage: auto message <text>")
continue
message = " ".join(parts[2:])
protocol.queue_auto_message(message)
print(f"{GREEN}[AUTO]{RESET} Message queued for sending: {message}")
elif subcmd == "passive":
# Configure as passive peer (responds but doesn't initiate)
protocol.configure_auto_mode(
ping_response_accept=True,
ping_auto_initiate=False,
active_mode=False
)
print(f"{GREEN}[AUTO]{RESET} Configured as passive peer")
elif subcmd == "active":
# Configure as active peer (initiates protocol)
protocol.configure_auto_mode(
ping_response_accept=True,
ping_auto_initiate=True,
active_mode=True
)
print(f"{GREEN}[AUTO]{RESET} Configured as active peer")
else:
print(f"{RED}[ERROR]{RESET} Unknown auto mode command: {subcmd}")
print("Available commands: start, stop, status, config, message, passive, active")
# Legacy commands
elif cmd == "auto_responder":
if len(parts) != 2:
print(f"{RED}[ERROR]{RESET} Usage: auto_responder <on|off>")
@ -173,6 +309,7 @@ def main():
print(f"{RED}[ERROR]{RESET} Value must be 'on' or 'off'.")
continue
protocol.enable_auto_responder(val == "on")
print(f"{YELLOW}[WARNING]{RESET} Using legacy auto responder. Consider using 'auto' commands instead.")
else:
print(f"{RED}[ERROR]{RESET} Unknown command: {cmd}")

View File

@ -23,6 +23,7 @@ from encryption import (
EncryptedMessage, MessageHeader,
generate_iv, encrypt_message, decrypt_message
)
from auto_mode import AutoMode, AutoModeConfig
# ANSI colors
RED = "\033[91m"
@ -63,9 +64,13 @@ class IcingProtocol:
"ping_received": False,
"handshake_sent": False,
"handshake_received": False,
"key_exchange_complete": False
}
# Auto-responder toggle
# Auto mode for automated protocol operation
self.auto_mode = AutoMode(self)
# Legacy auto-responder toggle (kept for backward compatibility)
self.auto_responder = False
# Active connections list
@ -96,6 +101,9 @@ class IcingProtocol:
def on_new_connection(self, conn: transmission.PeerConnection):
print(f"{GREEN}[IcingProtocol]{RESET} New incoming connection.")
self.connections.append(conn)
# Notify auto mode
self.auto_mode.handle_connection_established()
def on_data_received(self, conn: transmission.PeerConnection, data: bytes):
bits_count = len(data) * 8
@ -130,8 +138,11 @@ class IcingProtocol:
}
self.inbound_messages.append(msg)
# Auto-respond if enabled
if self.auto_responder:
# Handle in auto mode (if active)
self.auto_mode.handle_ping_received(index)
# Legacy auto-responder (for backward compatibility)
if self.auto_responder and not self.auto_mode.active:
timer = threading.Timer(2.0, self._auto_respond_ping, args=[index])
timer.daemon = True
timer.start()
@ -152,6 +163,9 @@ class IcingProtocol:
"connection": conn
}
self.inbound_messages.append(msg)
# Notify auto mode (if active)
self.auto_mode.handle_ping_response_received(ping_response.answer == 1)
return
# HANDSHAKE message (168 bytes)
@ -168,8 +182,11 @@ class IcingProtocol:
}
self.inbound_messages.append(msg)
# Auto-respond if enabled
if self.auto_responder:
# Notify auto mode (if active)
self.auto_mode.handle_handshake_received(index)
# Legacy auto-responder
if self.auto_responder and not self.auto_mode.active:
timer = threading.Timer(2.0, self._auto_respond_handshake, args=[index])
timer.daemon = True
timer.start()
@ -198,6 +215,9 @@ class IcingProtocol:
}
self.inbound_messages.append(msg)
print(f"{YELLOW}[NOTICE]{RESET} Stored inbound ENCRYPTED_MESSAGE at index={index}.")
# Notify auto mode
self.auto_mode.handle_encrypted_received(index)
return
except Exception as e:
print(f"{RED}[ERROR]{RESET} Failed to parse message header: {e}")
@ -269,10 +289,12 @@ class IcingProtocol:
)
derived_key = hkdf.derive(ikm)
self.hkdf_key = derived_key.hex()
self.state["key_exchange_complete"] = True
print(f"{GREEN}[HKDF]{RESET} Derived HKDF key: {self.hkdf_key}")
return True
# -------------------------------------------------------------------------
# Auto-responder helpers
# Legacy Auto-responder helpers (kept for backward compatibility)
# -------------------------------------------------------------------------
def _auto_respond_ping(self, index: int):
@ -306,6 +328,140 @@ class IcingProtocol:
# 4) Show final state
self.show_state()
# -------------------------------------------------------------------------
# Public Methods for Auto Mode Management
# -------------------------------------------------------------------------
def start_auto_mode(self):
"""Start the automatic protocol operation mode."""
self.auto_mode.start()
def stop_auto_mode(self):
"""Stop the automatic protocol operation mode."""
self.auto_mode.stop()
def configure_auto_mode(self, **kwargs):
"""
Configure the automatic mode parameters.
Args:
**kwargs: Configuration parameters to set. Supported parameters:
- ping_response_accept: bool, whether to accept incoming pings
- ping_auto_initiate: bool, whether to initiate pings on connection
- ping_retry_count: int, number of ping retries
- ping_retry_delay: float, seconds between ping retries
- ping_timeout: float, seconds to wait for ping response
- preferred_cipher: int, preferred cipher (0=AES-GCM, 1=ChaCha20-Poly1305)
- handshake_retry_count: int, number of handshake retries
- handshake_retry_delay: float, seconds between handshake retries
- handshake_timeout: float, seconds to wait for handshake
- auto_message_enabled: bool, whether to auto-send messages
- message_interval: float, seconds between auto messages
- message_content: str, default message content
- active_mode: bool, whether to actively initiate protocol
"""
for key, value in kwargs.items():
if hasattr(self.auto_mode.config, key):
setattr(self.auto_mode.config, key, value)
print(f"{BLUE}[CONFIG]{RESET} Set auto mode {key} = {value}")
else:
print(f"{RED}[ERROR]{RESET} Unknown auto mode configuration parameter: {key}")
def get_auto_mode_config(self):
"""Return the current auto mode configuration."""
return self.auto_mode.config
def queue_auto_message(self, message: str):
"""
Add a message to the auto-send queue.
Args:
message: Message text to send
"""
self.auto_mode.queue_message(message)
def toggle_auto_mode_logging(self):
"""
Toggle detailed logging for auto mode.
When enabled, will show more information about state transitions and decision making.
"""
if not hasattr(self.auto_mode, 'verbose_logging'):
self.auto_mode.verbose_logging = True
else:
self.auto_mode.verbose_logging = not self.auto_mode.verbose_logging
status = "enabled" if self.auto_mode.verbose_logging else "disabled"
print(f"{BLUE}[AUTO-LOG]{RESET} Detailed logging {status}")
def debug_message(self, index: int):
"""
Debug a message in the inbound message queue.
Prints detailed information about the message.
Args:
index: The index of the message in the inbound_messages queue
"""
if index < 0 or index >= len(self.inbound_messages):
print(f"{RED}[ERROR]{RESET} Invalid message index {index}")
return
msg = self.inbound_messages[index]
print(f"\n{YELLOW}=== Message Debug [{index}] ==={RESET}")
print(f"Type: {msg['type']}")
print(f"Length: {len(msg['raw'])} bytes = {len(msg['raw'])*8} bits")
print(f"Raw data: {msg['raw'].hex()}")
if msg['parsed'] is not None:
print(f"\n{YELLOW}--- Parsed Data ---{RESET}")
if msg['type'] == 'PING_REQUEST':
ping = msg['parsed']
print(f"Version: {ping.version}")
print(f"Cipher: {ping.cipher} ({'AES-256-GCM' if ping.cipher == 0 else 'ChaCha20-Poly1305' if ping.cipher == 1 else 'Unknown'})")
print(f"Session nonce: {ping.session_nonce.hex()}")
print(f"CRC32: {ping.crc32:08x}")
elif msg['type'] == 'PING_RESPONSE':
resp = msg['parsed']
print(f"Version: {resp.version}")
print(f"Cipher: {resp.cipher} ({'AES-256-GCM' if resp.cipher == 0 else 'ChaCha20-Poly1305' if resp.cipher == 1 else 'Unknown'})")
print(f"Answer: {resp.answer} ({'Accept' if resp.answer == 1 else 'Reject'})")
print(f"CRC32: {resp.crc32:08x}")
elif msg['type'] == 'HANDSHAKE':
hs = msg['parsed']
print(f"Ephemeral pubkey: {hs.ephemeral_pubkey.hex()[:16]}...")
print(f"Ephemeral signature: {hs.ephemeral_signature.hex()[:16]}...")
print(f"PFS hash: {hs.pfs_hash.hex()[:16]}...")
print(f"Timestamp: {hs.timestamp}")
print(f"CRC32: {hs.crc32:08x}")
elif msg['type'] == 'ENCRYPTED_MESSAGE':
header = msg['parsed']
print(f"Flag: 0x{header.flag:04x}")
print(f"Data length: {header.data_len} bytes")
print(f"Retry: {header.retry}")
print(f"Connection status: {header.connection_status} ({'CRC included' if header.connection_status & 0x01 else 'No CRC'})")
print(f"IV: {header.iv.hex()}")
# Calculate expected message size
expected_len = 18 + header.data_len + 16 # Header + payload + tag
if header.connection_status & 0x01:
expected_len += 4 # Add CRC
print(f"Expected total length: {expected_len} bytes")
print(f"Actual length: {len(msg['raw'])} bytes")
# If we have a key, try to decrypt
if self.hkdf_key:
print("\nAttempting decryption...")
try:
key = bytes.fromhex(self.hkdf_key)
plaintext = decrypt_message(msg['raw'], key, self.cipher_type)
print(f"Decrypted: {plaintext.decode('utf-8')}")
except Exception as e:
print(f"Decryption failed: {e}")
print()
# -------------------------------------------------------------------------
# Public Methods
# -------------------------------------------------------------------------
@ -314,6 +470,9 @@ class IcingProtocol:
conn = transmission.connect_to_peer("127.0.0.1", port, self.on_data_received)
self.connections.append(conn)
print(f"{GREEN}[IcingProtocol]{RESET} Outgoing connection to port {port} established.")
# Notify auto mode
self.auto_mode.handle_connection_established()
def set_peer_identity(self, peer_pubkey_hex: str):
pubkey_bytes = bytes.fromhex(peer_pubkey_hex)
@ -326,13 +485,24 @@ class IcingProtocol:
print(f"{GREEN}[IcingProtocol]{RESET} Generated ephemeral key pair: pubkey={self.ephemeral_pubkey.hex()[:16]}...")
# Send PING (session discovery and cipher negotiation)
def send_ping_request(self):
def send_ping_request(self, cipher_type=0):
"""
Send a ping request to the first connected peer.
Args:
cipher_type: Preferred cipher type (0 = AES-256-GCM, 1 = ChaCha20-Poly1305)
"""
if not self.connections:
print(f"{RED}[ERROR]{RESET} No active connections.")
return
return False
# Validate cipher type
if cipher_type not in (0, 1):
print(f"{YELLOW}[WARNING]{RESET} Invalid cipher type {cipher_type}, defaulting to AES-256-GCM (0)")
cipher_type = 0
# Create ping request with our default cipher preference (AES-256-GCM = 0)
ping_request = PingRequest(version=0, cipher=0)
# Create ping request with specified cipher
ping_request = PingRequest(version=0, cipher=cipher_type)
# Store session nonce if not already set
if self.session_nonce is None:
@ -343,6 +513,7 @@ class IcingProtocol:
pkt = ping_request.serialize()
self._send_packet(self.connections[0], pkt, "PING_REQUEST")
self.state["ping_sent"] = True
return True
def send_handshake(self):
"""
@ -355,13 +526,13 @@ class IcingProtocol:
"""
if not self.connections:
print(f"{RED}[ERROR]{RESET} No active connections.")
return
return False
if not self.ephemeral_privkey or not self.ephemeral_pubkey:
print(f"{RED}[ERROR]{RESET} Ephemeral keys not generated.")
return
return False
if self.peer_identity_pubkey_bytes is None:
print(f"{RED}[ERROR]{RESET} Peer identity not set; needed for PFS tracking.")
return
return False
# 1) Sign ephemeral_pubkey using identity key
sig_der = sign_data(self.identity_privkey, self.ephemeral_pubkey)
@ -383,10 +554,15 @@ class IcingProtocol:
pkt = handshake.serialize()
self._send_packet(self.connections[0], pkt, "HANDSHAKE")
self.state["handshake_sent"] = True
return True
def enable_auto_responder(self, enable: bool):
"""
Legacy method for enabling/disabling auto responder.
For new code, use start_auto_mode() and stop_auto_mode() instead.
"""
self.auto_responder = enable
print(f"{BLUE}[AUTO]{RESET} Auto responder set to {enable}.")
print(f"{YELLOW}[LEGACY]{RESET} Auto responder set to {enable}. Consider using auto_mode instead.")
# -------------------------------------------------------------------------
# Manual Responses
@ -399,11 +575,11 @@ class IcingProtocol:
"""
if index < 0 or index >= len(self.inbound_messages):
print(f"{RED}[ERROR]{RESET} Invalid index {index}.")
return
return False
msg = self.inbound_messages[index]
if msg["type"] != "PING_REQUEST":
print(f"{RED}[ERROR]{RESET} inbound_messages[{index}] is not a PING_REQUEST.")
return
return False
ping_request = msg["parsed"]
version = ping_request.version
@ -424,6 +600,7 @@ class IcingProtocol:
resp = ping_response.serialize()
self._send_packet(conn, resp, "PING_RESPONSE")
print(f"{BLUE}[MANUAL]{RESET} Responded to ping with answer={answer}.")
return True
def generate_ecdhe(self, index: int):
"""
@ -434,11 +611,11 @@ class IcingProtocol:
"""
if index < 0 or index >= len(self.inbound_messages):
print(f"{RED}[ERROR]{RESET} Invalid index {index}.")
return
return False
msg = self.inbound_messages[index]
if msg["type"] != "HANDSHAKE":
print(f"{RED}[ERROR]{RESET} inbound_messages[{index}] is not a HANDSHAKE.")
return
return False
handshake = msg["parsed"]
@ -450,13 +627,13 @@ class IcingProtocol:
ok = verify_signature(self.peer_identity_pubkey_obj, sig_der, handshake.ephemeral_pubkey)
if not ok:
print(f"{RED}[ERROR]{RESET} Ephemeral signature invalid.")
return
return False
print(f"{GREEN}[OK]{RESET} Ephemeral signature verified.")
# Check if we have ephemeral keys
if not self.ephemeral_privkey:
print(f"{YELLOW}[WARN]{RESET} No ephemeral_privkey available, cannot compute shared secret.")
return
return False
# Compute ECDH shared secret
shared = compute_ecdh_shared_key(self.ephemeral_privkey, handshake.ephemeral_pubkey)
@ -467,6 +644,7 @@ class IcingProtocol:
old_session, _ = self.pfs_history.get(self.peer_identity_pubkey_bytes, (-1, ""))
new_session = 1 if old_session < 0 else old_session + 1
self.pfs_history[self.peer_identity_pubkey_bytes] = (new_session, self.shared_secret)
return True
# -------------------------------------------------------------------------
# Utility
@ -516,7 +694,9 @@ class IcingProtocol:
for k, v in self.state.items():
print(f" {k}: {v}")
print("\nAuto Responder:", self.auto_responder)
print("\nAuto Mode Active:", self.auto_mode.active)
print("Auto Mode State:", self.auto_mode.state)
print("Legacy Auto Responder:", self.auto_responder)
print("\nActive Connections:")
for i, c in enumerate(self.connections):
@ -528,14 +708,24 @@ class IcingProtocol:
print()
def stop(self):
"""Stop the protocol and clean up resources."""
# Stop auto mode first
self.auto_mode.stop()
# Stop server listener
self.server_listener.stop()
# Close all connections
for c in self.connections:
c.close()
self.connections.clear()
self.inbound_messages.clear()
print(f"{RED}[STOP]{RESET} Protocol stopped.")
# New method: Send an encrypted message over the first active connection.
# -------------------------------------------------------------------------
# Encrypted Messaging
# -------------------------------------------------------------------------
def send_encrypted_message(self, plaintext: str):
"""
Encrypts and sends a message using the derived HKDF key and negotiated cipher.
@ -546,10 +736,10 @@ class IcingProtocol:
"""
if not self.connections:
print(f"{RED}[ERROR]{RESET} No active connections.")
return
return False
if not self.hkdf_key:
print(f"{RED}[ERROR]{RESET} No HKDF key derived. Cannot encrypt message.")
return
return False
# Get the encryption key
key = bytes.fromhex(self.hkdf_key)
@ -582,27 +772,27 @@ class IcingProtocol:
# Send the encrypted message
self._send_packet(self.connections[0], encrypted, "ENCRYPTED_MESSAGE")
print(f"{GREEN}[SEND_ENCRYPTED]{RESET} Encrypted message sent.")
return True
# New method: Decrypt an encrypted message from the inbound queue.
def decrypt_received_message(self, index: int):
"""
Decrypt a received encrypted message using the HKDF key and negotiated cipher.
"""
if index < 0 or index >= len(self.inbound_messages):
print(f"{RED}[ERROR]{RESET} Invalid message index.")
return
return None
msg = self.inbound_messages[index]
if msg["type"] != "ENCRYPTED_MESSAGE":
print(f"{RED}[ERROR]{RESET} Message at index {index} is not an ENCRYPTED_MESSAGE.")
return
return None
# Get the encrypted message
encrypted = msg["raw"]
if not self.hkdf_key:
print(f"{RED}[ERROR]{RESET} No HKDF key derived. Cannot decrypt message.")
return
return None
# Get the encryption key
key = bytes.fromhex(self.hkdf_key)