// Package tor - Embedded Tor implementation for military/war scenarios package tor import ( "crypto/rand" "encoding/hex" "fmt" "io" "net" "os" "os/exec" "path/filepath" "runtime" "strings" "sync" "time" ) // EmbeddedTor manages an embedded Tor instance type EmbeddedTor struct { torBinary string // Path to Tor executable torDataDir string // Tor data directory torProcess *exec.Cmd // Tor subprocess controlPort int // Tor control port socksPort int // Tor SOCKS proxy port hiddenSvcDir string // Hidden service directory onionAddress string // Our .onion address running bool mu sync.RWMutex useBridges bool // Use bridges for censorship circumvention bridges []string // Bridge addresses } // EmbeddedTorConfig holds configuration for embedded Tor type EmbeddedTorConfig struct { DataDir string // Where to store Tor data UseBridges bool // Enable bridge mode (anti-censorship) Bridges []string // Bridge addresses (obfs4, meek, etc.) ControlPort int // Control port (0 = auto) SocksPort int // SOCKS proxy port (0 = auto) AutoStart bool // Auto-start Tor on creation } // NewEmbeddedTor creates a new embedded Tor instance func NewEmbeddedTor(config *EmbeddedTorConfig) (*EmbeddedTor, error) { if config == nil { config = &EmbeddedTorConfig{ AutoStart: true, ControlPort: 0, // Auto-select SocksPort: 0, // Auto-select } } // Create data directory dataDir := config.DataDir if dataDir == "" { // Use temp directory tmpDir := os.TempDir() dataDir = filepath.Join(tmpDir, "khimera-tor-"+randomString(8)) } if err := os.MkdirAll(dataDir, 0700); err != nil { return nil, fmt.Errorf("failed to create Tor data dir: %w", err) } et := &EmbeddedTor{ torDataDir: dataDir, hiddenSvcDir: filepath.Join(dataDir, "hidden_service"), controlPort: config.ControlPort, socksPort: config.SocksPort, useBridges: config.UseBridges, bridges: config.Bridges, } // Find Tor binary torBin, err := et.findTorBinary() if err != nil { return nil, fmt.Errorf("Tor binary not found: %w", err) } et.torBinary = torBin // Auto-assign ports if not specified if et.controlPort == 0 { et.controlPort = findFreePort() } if et.socksPort == 0 { et.socksPort = findFreePort() } // Auto-start if requested if config.AutoStart { if err := et.Start(); err != nil { return nil, fmt.Errorf("failed to auto-start Tor: %w", err) } } return et, nil } // findTorBinary locates the Tor binary func (et *EmbeddedTor) findTorBinary() (string, error) { // Try common locations possiblePaths := []string{ "tor", // PATH "/usr/bin/tor", // Linux "/usr/local/bin/tor", // macOS/BSD "C:\\Program Files\\Tor Browser\\tor.exe", // Windows filepath.Join(et.torDataDir, "tor"), // Bundled with app } // Add OS-specific paths if runtime.GOOS == "windows" { possiblePaths = append(possiblePaths, "tor.exe") } for _, path := range possiblePaths { if _, err := os.Stat(path); err == nil { return path, nil } // Also try in PATH if fullPath, err := exec.LookPath(path); err == nil { return fullPath, nil } } return "", fmt.Errorf("Tor binary not found in common locations") } // Start starts the embedded Tor process func (et *EmbeddedTor) Start() error { et.mu.Lock() defer et.mu.Unlock() if et.running { return fmt.Errorf("Tor already running") } // Create torrc configuration torrcPath := filepath.Join(et.torDataDir, "torrc") if err := et.writeTorrc(torrcPath); err != nil { return fmt.Errorf("failed to write torrc: %w", err) } // Launch Tor process et.torProcess = exec.Command(et.torBinary, "-f", torrcPath) et.torProcess.Stdout = os.Stdout // For debugging et.torProcess.Stderr = os.Stderr if err := et.torProcess.Start(); err != nil { return fmt.Errorf("failed to start Tor: %w", err) } et.running = true // Wait for Tor to bootstrap if err := et.waitForBootstrap(60 * time.Second); err != nil { et.Stop() return fmt.Errorf("Tor bootstrap failed: %w", err) } // Read hidden service address if err := et.readOnionAddress(); err != nil { return fmt.Errorf("failed to read onion address: %w", err) } return nil } // writeTorrc writes Tor configuration file func (et *EmbeddedTor) writeTorrc(path string) error { config := fmt.Sprintf(`# Khimera Embedded Tor Configuration # Data directory DataDirectory %s # SOCKS proxy SOCKSPort %d # Control port ControlPort %d # Hidden service HiddenServiceDir %s HiddenServicePort 8083 127.0.0.1:8083 # Security settings SafeLogging 1 AvoidDiskWrites 1 `, et.torDataDir, et.socksPort, et.controlPort, et.hiddenSvcDir) // Add bridges if anti-censorship mode if et.useBridges && len(et.bridges) > 0 { config += "\n# Bridge mode (anti-censorship)\n" config += "UseBridges 1\n" config += "ClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy\n" for _, bridge := range et.bridges { config += fmt.Sprintf("Bridge %s\n", bridge) } } return os.WriteFile(path, []byte(config), 0600) } // waitForBootstrap waits for Tor to finish bootstrapping func (et *EmbeddedTor) waitForBootstrap(timeout time.Duration) error { start := time.Now() for { if time.Since(start) > timeout { return fmt.Errorf("bootstrap timeout") } // Try to connect to SOCKS port conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", et.socksPort), 1*time.Second) if err == nil { conn.Close() // Successfully connected - Tor is ready return nil } time.Sleep(1 * time.Second) } } // readOnionAddress reads the hidden service .onion address func (et *EmbeddedTor) readOnionAddress() error { hostnamePath := filepath.Join(et.hiddenSvcDir, "hostname") // Wait for file to be created (may take a few seconds) for i := 0; i < 30; i++ { data, err := os.ReadFile(hostnamePath) if err == nil { et.onionAddress = strings.TrimSpace(string(data)) return nil } time.Sleep(1 * time.Second) } return fmt.Errorf("failed to read onion address from %s", hostnamePath) } // Stop stops the embedded Tor process func (et *EmbeddedTor) Stop() error { et.mu.Lock() defer et.mu.Unlock() if !et.running { return nil } if et.torProcess != nil { // Graceful shutdown if err := et.torProcess.Process.Signal(os.Interrupt); err != nil { // Force kill if graceful fails et.torProcess.Process.Kill() } // Wait for process to exit et.torProcess.Wait() } et.running = false return nil } // GetSOCKSProxy returns the SOCKS proxy address func (et *EmbeddedTor) GetSOCKSProxy() string { et.mu.RLock() defer et.mu.RUnlock() return fmt.Sprintf("127.0.0.1:%d", et.socksPort) } // GetOnionAddress returns our hidden service .onion address func (et *EmbeddedTor) GetOnionAddress() string { et.mu.RLock() defer et.mu.RUnlock() return et.onionAddress } // IsRunning returns whether Tor is currently running func (et *EmbeddedTor) IsRunning() bool { et.mu.RLock() defer et.mu.RUnlock() return et.running } // Cleanup removes Tor data directory func (et *EmbeddedTor) Cleanup() error { et.Stop() if et.torDataDir != "" { return os.RemoveAll(et.torDataDir) } return nil } // SetBridges configures Tor bridges for censorship circumvention func (et *EmbeddedTor) SetBridges(bridges []string) error { et.mu.Lock() defer et.mu.Unlock() et.useBridges = true et.bridges = bridges // Restart Tor if running to apply new config if et.running { et.mu.Unlock() // Unlock before calling Stop (which locks) et.Stop() et.mu.Lock() return et.Start() } return nil } // GetDefaultBridges returns a list of default obfs4 bridges func GetDefaultBridges() []string { return []string{ "obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1", "obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0", "obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0", } } // findFreePort finds an available TCP port func findFreePort() int { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return 9050 // Fallback to default } defer listener.Close() addr := listener.Addr().(*net.TCPAddr) return addr.Port } // randomString generates a random hex string func randomString(n int) string { bytes := make([]byte, n) io.ReadFull(rand.Reader, bytes) return hex.EncodeToString(bytes) } // String returns a string representation func (et *EmbeddedTor) String() string { et.mu.RLock() defer et.mu.RUnlock() status := "stopped" if et.running { status = "running" } return fmt.Sprintf("EmbeddedTor(status=%s, socks=%d, onion=%s)", status, et.socksPort, et.onionAddress) }