summaryrefslogtreecommitdiffstats
path: root/transport/tor/embedded.go
diff options
context:
space:
mode:
Diffstat (limited to 'transport/tor/embedded.go')
-rw-r--r--transport/tor/embedded.go357
1 files changed, 357 insertions, 0 deletions
diff --git a/transport/tor/embedded.go b/transport/tor/embedded.go
new file mode 100644
index 0000000..c1800d8
--- /dev/null
+++ b/transport/tor/embedded.go
@@ -0,0 +1,357 @@
+// 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, "veilith-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(`# Veilith 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)
+}