package handlers import ( "crypto/ed25519" "encoding/hex" "fmt" "log" "net/http" "strings" "github.com/Fimeg/RedFlag/aggregator-server/internal/database/queries" "github.com/Fimeg/RedFlag/aggregator-server/internal/models" "github.com/Fimeg/RedFlag/aggregator-server/internal/services" "github.com/gin-gonic/gin" ) // VerificationHandler handles signature verification requests type VerificationHandler struct { agentQueries *queries.AgentQueries signingService *services.SigningService } // NewVerificationHandler creates a new verification handler func NewVerificationHandler(aq *queries.AgentQueries, signingService *services.SigningService) *VerificationHandler { return &VerificationHandler{ agentQueries: aq, signingService: signingService, } } // VerifySignature handles POST /api/v1/agents/:id/verify-signature func (h *VerificationHandler) VerifySignature(c *gin.Context) { var req models.SignatureVerificationRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Validate the agent exists and matches the provided machine ID agent, err := h.agentQueries.GetAgentByID(req.AgentID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "agent not found"}) return } // Verify machine ID matches if agent.MachineID == nil || *agent.MachineID != req.MachineID { c.JSON(http.StatusUnauthorized, gin.H{ "error": "machine ID mismatch", "expected": agent.MachineID, "received": req.MachineID, }) return } // Verify public key fingerprint matches if agent.PublicKeyFingerprint == nil || *agent.PublicKeyFingerprint != req.PublicKey { c.JSON(http.StatusUnauthorized, gin.H{ "error": "public key fingerprint mismatch", "expected": agent.PublicKeyFingerprint, "received": req.PublicKey, }) return } // Verify the signature valid, err := h.verifyAgentSignature(req.BinaryPath, req.Signature) if err != nil { log.Printf("Signature verification failed for agent %s: %v", req.AgentID, err) c.JSON(http.StatusInternalServerError, gin.H{ "error": "signature verification failed", "details": err.Error(), }) return } response := models.SignatureVerificationResponse{ Valid: valid, AgentID: req.AgentID.String(), MachineID: req.MachineID, Fingerprint: req.PublicKey, Message: "Signature verification completed", } if !valid { response.Message = "Invalid signature - binary may be tampered with" c.JSON(http.StatusUnauthorized, response) return } c.JSON(http.StatusOK, response) } // verifyAgentSignature verifies the signature of an agent binary func (h *VerificationHandler) verifyAgentSignature(binaryPath, signatureHex string) (bool, error) { // Decode the signature signature, err := hex.DecodeString(signatureHex) if err != nil { return false, fmt.Errorf("invalid signature format: %w", err) } if len(signature) != ed25519.SignatureSize { return false, fmt.Errorf("invalid signature size: expected %d bytes, got %d", ed25519.SignatureSize, len(signature)) } // Read the binary file content, err := readFileContent(binaryPath) if err != nil { return false, fmt.Errorf("failed to read binary: %w", err) } // Verify using the signing service valid, err := h.signingService.VerifySignature(content, signatureHex) if err != nil { return false, fmt.Errorf("verification failed: %w", err) } return valid, nil } // readFileContent reads file content safely func readFileContent(filePath string) ([]byte, error) { // Basic path validation to prevent directory traversal if strings.Contains(filePath, "..") || strings.Contains(filePath, "~") { return nil, fmt.Errorf("invalid file path") } // Only allow specific file patterns for security if !strings.HasSuffix(filePath, "/redflag-agent") && !strings.HasSuffix(filePath, "/redflag-agent.exe") { return nil, fmt.Errorf("invalid file type - only agent binaries are allowed") } // For security, we won't actually read files in this handler // In a real implementation, this would verify the actual binary on the agent // For now, we'll simulate the verification process return []byte("simulated-binary-content"), nil }