This commit is contained in:
parent
0b52d602ef
commit
71b9cc787b
430
protocol_prototype/auto_mode.py
Normal file
430
protocol_prototype/auto_mode.py
Normal 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}")
|
@ -1,12 +1,53 @@
|
|||||||
import sys
|
import sys
|
||||||
|
import argparse
|
||||||
|
import shlex
|
||||||
from protocol import IcingProtocol
|
from protocol import IcingProtocol
|
||||||
|
|
||||||
RED = "\033[91m"
|
RED = "\033[91m"
|
||||||
GREEN = "\033[92m"
|
GREEN = "\033[92m"
|
||||||
YELLOW = "\033[93m"
|
YELLOW = "\033[93m"
|
||||||
BLUE = "\033[94m"
|
BLUE = "\033[94m"
|
||||||
|
MAGENTA = "\033[95m"
|
||||||
|
CYAN = "\033[96m"
|
||||||
RESET = "\033[0m"
|
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():
|
def main():
|
||||||
protocol = IcingProtocol()
|
protocol = IcingProtocol()
|
||||||
@ -16,54 +57,27 @@ def main():
|
|||||||
print("======================================\n" + RESET)
|
print("======================================\n" + RESET)
|
||||||
print(f"Listening on port: {protocol.local_port}")
|
print(f"Listening on port: {protocol.local_port}")
|
||||||
print(f"Your identity public key (hex): {protocol.identity_pubkey.hex()}")
|
print(f"Your identity public key (hex): {protocol.identity_pubkey.hex()}")
|
||||||
print("\nAvailable commands:")
|
print_help()
|
||||||
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")
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
line = input("Cmd> ").strip()
|
line = input(f"{MAGENTA}Cmd>{RESET} ").strip()
|
||||||
except EOFError:
|
except EOFError:
|
||||||
break
|
break
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
parts = line.split()
|
parts = shlex.split(line) # Handle quoted arguments properly
|
||||||
cmd = parts[0].lower()
|
cmd = parts[0].lower()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Basic commands
|
||||||
if cmd == "exit":
|
if cmd == "exit":
|
||||||
protocol.stop()
|
protocol.stop()
|
||||||
break
|
break
|
||||||
|
|
||||||
elif cmd == "help":
|
elif cmd == "help":
|
||||||
print("\nAvailable commands:")
|
print_help()
|
||||||
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")
|
|
||||||
|
|
||||||
elif cmd == "show_state":
|
elif cmd == "show_state":
|
||||||
protocol.show_state()
|
protocol.show_state()
|
||||||
@ -88,7 +102,8 @@ def main():
|
|||||||
print(f"{RED}[ERROR]{RESET} Invalid port number.")
|
print(f"{RED}[ERROR]{RESET} Invalid port number.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{RED}[ERROR]{RESET} Connection failed: {e}")
|
print(f"{RED}[ERROR]{RESET} Connection failed: {e}")
|
||||||
|
|
||||||
|
# Manual protocol operation
|
||||||
elif cmd == "generate_ephemeral_keys":
|
elif cmd == "generate_ephemeral_keys":
|
||||||
protocol.generate_ephemeral_keys()
|
protocol.generate_ephemeral_keys()
|
||||||
|
|
||||||
@ -103,7 +118,7 @@ def main():
|
|||||||
cipher = 0
|
cipher = 0
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print(f"{YELLOW}[WARNING]{RESET} Invalid cipher code. Using AES-GCM (0).")
|
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":
|
elif cmd == "send_handshake":
|
||||||
protocol.send_handshake()
|
protocol.send_handshake()
|
||||||
@ -164,6 +179,127 @@ def main():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{RED}[ERROR]{RESET} Failed to decrypt message: {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":
|
elif cmd == "auto_responder":
|
||||||
if len(parts) != 2:
|
if len(parts) != 2:
|
||||||
print(f"{RED}[ERROR]{RESET} Usage: auto_responder <on|off>")
|
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'.")
|
print(f"{RED}[ERROR]{RESET} Value must be 'on' or 'off'.")
|
||||||
continue
|
continue
|
||||||
protocol.enable_auto_responder(val == "on")
|
protocol.enable_auto_responder(val == "on")
|
||||||
|
print(f"{YELLOW}[WARNING]{RESET} Using legacy auto responder. Consider using 'auto' commands instead.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"{RED}[ERROR]{RESET} Unknown command: {cmd}")
|
print(f"{RED}[ERROR]{RESET} Unknown command: {cmd}")
|
||||||
|
@ -23,6 +23,7 @@ from encryption import (
|
|||||||
EncryptedMessage, MessageHeader,
|
EncryptedMessage, MessageHeader,
|
||||||
generate_iv, encrypt_message, decrypt_message
|
generate_iv, encrypt_message, decrypt_message
|
||||||
)
|
)
|
||||||
|
from auto_mode import AutoMode, AutoModeConfig
|
||||||
|
|
||||||
# ANSI colors
|
# ANSI colors
|
||||||
RED = "\033[91m"
|
RED = "\033[91m"
|
||||||
@ -63,9 +64,13 @@ class IcingProtocol:
|
|||||||
"ping_received": False,
|
"ping_received": False,
|
||||||
"handshake_sent": False,
|
"handshake_sent": False,
|
||||||
"handshake_received": 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
|
self.auto_responder = False
|
||||||
|
|
||||||
# Active connections list
|
# Active connections list
|
||||||
@ -96,6 +101,9 @@ class IcingProtocol:
|
|||||||
def on_new_connection(self, conn: transmission.PeerConnection):
|
def on_new_connection(self, conn: transmission.PeerConnection):
|
||||||
print(f"{GREEN}[IcingProtocol]{RESET} New incoming connection.")
|
print(f"{GREEN}[IcingProtocol]{RESET} New incoming connection.")
|
||||||
self.connections.append(conn)
|
self.connections.append(conn)
|
||||||
|
|
||||||
|
# Notify auto mode
|
||||||
|
self.auto_mode.handle_connection_established()
|
||||||
|
|
||||||
def on_data_received(self, conn: transmission.PeerConnection, data: bytes):
|
def on_data_received(self, conn: transmission.PeerConnection, data: bytes):
|
||||||
bits_count = len(data) * 8
|
bits_count = len(data) * 8
|
||||||
@ -130,8 +138,11 @@ class IcingProtocol:
|
|||||||
}
|
}
|
||||||
self.inbound_messages.append(msg)
|
self.inbound_messages.append(msg)
|
||||||
|
|
||||||
# Auto-respond if enabled
|
# Handle in auto mode (if active)
|
||||||
if self.auto_responder:
|
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 = threading.Timer(2.0, self._auto_respond_ping, args=[index])
|
||||||
timer.daemon = True
|
timer.daemon = True
|
||||||
timer.start()
|
timer.start()
|
||||||
@ -152,6 +163,9 @@ class IcingProtocol:
|
|||||||
"connection": conn
|
"connection": conn
|
||||||
}
|
}
|
||||||
self.inbound_messages.append(msg)
|
self.inbound_messages.append(msg)
|
||||||
|
|
||||||
|
# Notify auto mode (if active)
|
||||||
|
self.auto_mode.handle_ping_response_received(ping_response.answer == 1)
|
||||||
return
|
return
|
||||||
|
|
||||||
# HANDSHAKE message (168 bytes)
|
# HANDSHAKE message (168 bytes)
|
||||||
@ -168,8 +182,11 @@ class IcingProtocol:
|
|||||||
}
|
}
|
||||||
self.inbound_messages.append(msg)
|
self.inbound_messages.append(msg)
|
||||||
|
|
||||||
# Auto-respond if enabled
|
# Notify auto mode (if active)
|
||||||
if self.auto_responder:
|
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 = threading.Timer(2.0, self._auto_respond_handshake, args=[index])
|
||||||
timer.daemon = True
|
timer.daemon = True
|
||||||
timer.start()
|
timer.start()
|
||||||
@ -198,6 +215,9 @@ class IcingProtocol:
|
|||||||
}
|
}
|
||||||
self.inbound_messages.append(msg)
|
self.inbound_messages.append(msg)
|
||||||
print(f"{YELLOW}[NOTICE]{RESET} Stored inbound ENCRYPTED_MESSAGE at index={index}.")
|
print(f"{YELLOW}[NOTICE]{RESET} Stored inbound ENCRYPTED_MESSAGE at index={index}.")
|
||||||
|
|
||||||
|
# Notify auto mode
|
||||||
|
self.auto_mode.handle_encrypted_received(index)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{RED}[ERROR]{RESET} Failed to parse message header: {e}")
|
print(f"{RED}[ERROR]{RESET} Failed to parse message header: {e}")
|
||||||
@ -269,10 +289,12 @@ class IcingProtocol:
|
|||||||
)
|
)
|
||||||
derived_key = hkdf.derive(ikm)
|
derived_key = hkdf.derive(ikm)
|
||||||
self.hkdf_key = derived_key.hex()
|
self.hkdf_key = derived_key.hex()
|
||||||
|
self.state["key_exchange_complete"] = True
|
||||||
print(f"{GREEN}[HKDF]{RESET} Derived HKDF key: {self.hkdf_key}")
|
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):
|
def _auto_respond_ping(self, index: int):
|
||||||
@ -306,6 +328,140 @@ class IcingProtocol:
|
|||||||
# 4) Show final state
|
# 4) Show final state
|
||||||
self.show_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
|
# Public Methods
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@ -314,6 +470,9 @@ class IcingProtocol:
|
|||||||
conn = transmission.connect_to_peer("127.0.0.1", port, self.on_data_received)
|
conn = transmission.connect_to_peer("127.0.0.1", port, self.on_data_received)
|
||||||
self.connections.append(conn)
|
self.connections.append(conn)
|
||||||
print(f"{GREEN}[IcingProtocol]{RESET} Outgoing connection to port {port} established.")
|
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):
|
def set_peer_identity(self, peer_pubkey_hex: str):
|
||||||
pubkey_bytes = bytes.fromhex(peer_pubkey_hex)
|
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]}...")
|
print(f"{GREEN}[IcingProtocol]{RESET} Generated ephemeral key pair: pubkey={self.ephemeral_pubkey.hex()[:16]}...")
|
||||||
|
|
||||||
# Send PING (session discovery and cipher negotiation)
|
# 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:
|
if not self.connections:
|
||||||
print(f"{RED}[ERROR]{RESET} No active 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)
|
# Create ping request with specified cipher
|
||||||
ping_request = PingRequest(version=0, cipher=0)
|
ping_request = PingRequest(version=0, cipher=cipher_type)
|
||||||
|
|
||||||
# Store session nonce if not already set
|
# Store session nonce if not already set
|
||||||
if self.session_nonce is None:
|
if self.session_nonce is None:
|
||||||
@ -343,6 +513,7 @@ class IcingProtocol:
|
|||||||
pkt = ping_request.serialize()
|
pkt = ping_request.serialize()
|
||||||
self._send_packet(self.connections[0], pkt, "PING_REQUEST")
|
self._send_packet(self.connections[0], pkt, "PING_REQUEST")
|
||||||
self.state["ping_sent"] = True
|
self.state["ping_sent"] = True
|
||||||
|
return True
|
||||||
|
|
||||||
def send_handshake(self):
|
def send_handshake(self):
|
||||||
"""
|
"""
|
||||||
@ -355,13 +526,13 @@ class IcingProtocol:
|
|||||||
"""
|
"""
|
||||||
if not self.connections:
|
if not self.connections:
|
||||||
print(f"{RED}[ERROR]{RESET} No active connections.")
|
print(f"{RED}[ERROR]{RESET} No active connections.")
|
||||||
return
|
return False
|
||||||
if not self.ephemeral_privkey or not self.ephemeral_pubkey:
|
if not self.ephemeral_privkey or not self.ephemeral_pubkey:
|
||||||
print(f"{RED}[ERROR]{RESET} Ephemeral keys not generated.")
|
print(f"{RED}[ERROR]{RESET} Ephemeral keys not generated.")
|
||||||
return
|
return False
|
||||||
if self.peer_identity_pubkey_bytes is None:
|
if self.peer_identity_pubkey_bytes is None:
|
||||||
print(f"{RED}[ERROR]{RESET} Peer identity not set; needed for PFS tracking.")
|
print(f"{RED}[ERROR]{RESET} Peer identity not set; needed for PFS tracking.")
|
||||||
return
|
return False
|
||||||
|
|
||||||
# 1) Sign ephemeral_pubkey using identity key
|
# 1) Sign ephemeral_pubkey using identity key
|
||||||
sig_der = sign_data(self.identity_privkey, self.ephemeral_pubkey)
|
sig_der = sign_data(self.identity_privkey, self.ephemeral_pubkey)
|
||||||
@ -383,10 +554,15 @@ class IcingProtocol:
|
|||||||
pkt = handshake.serialize()
|
pkt = handshake.serialize()
|
||||||
self._send_packet(self.connections[0], pkt, "HANDSHAKE")
|
self._send_packet(self.connections[0], pkt, "HANDSHAKE")
|
||||||
self.state["handshake_sent"] = True
|
self.state["handshake_sent"] = True
|
||||||
|
return True
|
||||||
|
|
||||||
def enable_auto_responder(self, enable: bool):
|
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
|
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
|
# Manual Responses
|
||||||
@ -399,11 +575,11 @@ class IcingProtocol:
|
|||||||
"""
|
"""
|
||||||
if index < 0 or index >= len(self.inbound_messages):
|
if index < 0 or index >= len(self.inbound_messages):
|
||||||
print(f"{RED}[ERROR]{RESET} Invalid index {index}.")
|
print(f"{RED}[ERROR]{RESET} Invalid index {index}.")
|
||||||
return
|
return False
|
||||||
msg = self.inbound_messages[index]
|
msg = self.inbound_messages[index]
|
||||||
if msg["type"] != "PING_REQUEST":
|
if msg["type"] != "PING_REQUEST":
|
||||||
print(f"{RED}[ERROR]{RESET} inbound_messages[{index}] is not a PING_REQUEST.")
|
print(f"{RED}[ERROR]{RESET} inbound_messages[{index}] is not a PING_REQUEST.")
|
||||||
return
|
return False
|
||||||
|
|
||||||
ping_request = msg["parsed"]
|
ping_request = msg["parsed"]
|
||||||
version = ping_request.version
|
version = ping_request.version
|
||||||
@ -424,6 +600,7 @@ class IcingProtocol:
|
|||||||
resp = ping_response.serialize()
|
resp = ping_response.serialize()
|
||||||
self._send_packet(conn, resp, "PING_RESPONSE")
|
self._send_packet(conn, resp, "PING_RESPONSE")
|
||||||
print(f"{BLUE}[MANUAL]{RESET} Responded to ping with answer={answer}.")
|
print(f"{BLUE}[MANUAL]{RESET} Responded to ping with answer={answer}.")
|
||||||
|
return True
|
||||||
|
|
||||||
def generate_ecdhe(self, index: int):
|
def generate_ecdhe(self, index: int):
|
||||||
"""
|
"""
|
||||||
@ -434,11 +611,11 @@ class IcingProtocol:
|
|||||||
"""
|
"""
|
||||||
if index < 0 or index >= len(self.inbound_messages):
|
if index < 0 or index >= len(self.inbound_messages):
|
||||||
print(f"{RED}[ERROR]{RESET} Invalid index {index}.")
|
print(f"{RED}[ERROR]{RESET} Invalid index {index}.")
|
||||||
return
|
return False
|
||||||
msg = self.inbound_messages[index]
|
msg = self.inbound_messages[index]
|
||||||
if msg["type"] != "HANDSHAKE":
|
if msg["type"] != "HANDSHAKE":
|
||||||
print(f"{RED}[ERROR]{RESET} inbound_messages[{index}] is not a HANDSHAKE.")
|
print(f"{RED}[ERROR]{RESET} inbound_messages[{index}] is not a HANDSHAKE.")
|
||||||
return
|
return False
|
||||||
|
|
||||||
handshake = msg["parsed"]
|
handshake = msg["parsed"]
|
||||||
|
|
||||||
@ -450,13 +627,13 @@ class IcingProtocol:
|
|||||||
ok = verify_signature(self.peer_identity_pubkey_obj, sig_der, handshake.ephemeral_pubkey)
|
ok = verify_signature(self.peer_identity_pubkey_obj, sig_der, handshake.ephemeral_pubkey)
|
||||||
if not ok:
|
if not ok:
|
||||||
print(f"{RED}[ERROR]{RESET} Ephemeral signature invalid.")
|
print(f"{RED}[ERROR]{RESET} Ephemeral signature invalid.")
|
||||||
return
|
return False
|
||||||
print(f"{GREEN}[OK]{RESET} Ephemeral signature verified.")
|
print(f"{GREEN}[OK]{RESET} Ephemeral signature verified.")
|
||||||
|
|
||||||
# Check if we have ephemeral keys
|
# Check if we have ephemeral keys
|
||||||
if not self.ephemeral_privkey:
|
if not self.ephemeral_privkey:
|
||||||
print(f"{YELLOW}[WARN]{RESET} No ephemeral_privkey available, cannot compute shared secret.")
|
print(f"{YELLOW}[WARN]{RESET} No ephemeral_privkey available, cannot compute shared secret.")
|
||||||
return
|
return False
|
||||||
|
|
||||||
# Compute ECDH shared secret
|
# Compute ECDH shared secret
|
||||||
shared = compute_ecdh_shared_key(self.ephemeral_privkey, handshake.ephemeral_pubkey)
|
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, ""))
|
old_session, _ = self.pfs_history.get(self.peer_identity_pubkey_bytes, (-1, ""))
|
||||||
new_session = 1 if old_session < 0 else old_session + 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)
|
self.pfs_history[self.peer_identity_pubkey_bytes] = (new_session, self.shared_secret)
|
||||||
|
return True
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Utility
|
# Utility
|
||||||
@ -516,7 +694,9 @@ class IcingProtocol:
|
|||||||
for k, v in self.state.items():
|
for k, v in self.state.items():
|
||||||
print(f" {k}: {v}")
|
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:")
|
print("\nActive Connections:")
|
||||||
for i, c in enumerate(self.connections):
|
for i, c in enumerate(self.connections):
|
||||||
@ -528,14 +708,24 @@ class IcingProtocol:
|
|||||||
print()
|
print()
|
||||||
|
|
||||||
def stop(self):
|
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()
|
self.server_listener.stop()
|
||||||
|
|
||||||
|
# Close all connections
|
||||||
for c in self.connections:
|
for c in self.connections:
|
||||||
c.close()
|
c.close()
|
||||||
self.connections.clear()
|
self.connections.clear()
|
||||||
self.inbound_messages.clear()
|
self.inbound_messages.clear()
|
||||||
print(f"{RED}[STOP]{RESET} Protocol stopped.")
|
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):
|
def send_encrypted_message(self, plaintext: str):
|
||||||
"""
|
"""
|
||||||
Encrypts and sends a message using the derived HKDF key and negotiated cipher.
|
Encrypts and sends a message using the derived HKDF key and negotiated cipher.
|
||||||
@ -546,10 +736,10 @@ class IcingProtocol:
|
|||||||
"""
|
"""
|
||||||
if not self.connections:
|
if not self.connections:
|
||||||
print(f"{RED}[ERROR]{RESET} No active connections.")
|
print(f"{RED}[ERROR]{RESET} No active connections.")
|
||||||
return
|
return False
|
||||||
if not self.hkdf_key:
|
if not self.hkdf_key:
|
||||||
print(f"{RED}[ERROR]{RESET} No HKDF key derived. Cannot encrypt message.")
|
print(f"{RED}[ERROR]{RESET} No HKDF key derived. Cannot encrypt message.")
|
||||||
return
|
return False
|
||||||
|
|
||||||
# Get the encryption key
|
# Get the encryption key
|
||||||
key = bytes.fromhex(self.hkdf_key)
|
key = bytes.fromhex(self.hkdf_key)
|
||||||
@ -582,27 +772,27 @@ class IcingProtocol:
|
|||||||
# Send the encrypted message
|
# Send the encrypted message
|
||||||
self._send_packet(self.connections[0], encrypted, "ENCRYPTED_MESSAGE")
|
self._send_packet(self.connections[0], encrypted, "ENCRYPTED_MESSAGE")
|
||||||
print(f"{GREEN}[SEND_ENCRYPTED]{RESET} Encrypted message sent.")
|
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):
|
def decrypt_received_message(self, index: int):
|
||||||
"""
|
"""
|
||||||
Decrypt a received encrypted message using the HKDF key and negotiated cipher.
|
Decrypt a received encrypted message using the HKDF key and negotiated cipher.
|
||||||
"""
|
"""
|
||||||
if index < 0 or index >= len(self.inbound_messages):
|
if index < 0 or index >= len(self.inbound_messages):
|
||||||
print(f"{RED}[ERROR]{RESET} Invalid message index.")
|
print(f"{RED}[ERROR]{RESET} Invalid message index.")
|
||||||
return
|
return None
|
||||||
|
|
||||||
msg = self.inbound_messages[index]
|
msg = self.inbound_messages[index]
|
||||||
if msg["type"] != "ENCRYPTED_MESSAGE":
|
if msg["type"] != "ENCRYPTED_MESSAGE":
|
||||||
print(f"{RED}[ERROR]{RESET} Message at index {index} is not an ENCRYPTED_MESSAGE.")
|
print(f"{RED}[ERROR]{RESET} Message at index {index} is not an ENCRYPTED_MESSAGE.")
|
||||||
return
|
return None
|
||||||
|
|
||||||
# Get the encrypted message
|
# Get the encrypted message
|
||||||
encrypted = msg["raw"]
|
encrypted = msg["raw"]
|
||||||
|
|
||||||
if not self.hkdf_key:
|
if not self.hkdf_key:
|
||||||
print(f"{RED}[ERROR]{RESET} No HKDF key derived. Cannot decrypt message.")
|
print(f"{RED}[ERROR]{RESET} No HKDF key derived. Cannot decrypt message.")
|
||||||
return
|
return None
|
||||||
|
|
||||||
# Get the encryption key
|
# Get the encryption key
|
||||||
key = bytes.fromhex(self.hkdf_key)
|
key = bytes.fromhex(self.hkdf_key)
|
||||||
|
Loading…
Reference in New Issue
Block a user