152 lines
4.4 KiB
Go
152 lines
4.4 KiB
Go
package crypto
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/Fimeg/RedFlag/aggregator-agent/internal/client"
|
|
)
|
|
|
|
// CommandVerifier handles Ed25519 signature verification for commands
|
|
type CommandVerifier struct {
|
|
// In the future, this could include:
|
|
// - Key rotation support
|
|
// - Multiple trusted keys
|
|
// - Revocation checking
|
|
}
|
|
|
|
// NewCommandVerifier creates a new command verifier
|
|
func NewCommandVerifier() *CommandVerifier {
|
|
return &CommandVerifier{}
|
|
}
|
|
|
|
// VerifyCommand verifies that a command's signature is valid
|
|
func (v *CommandVerifier) VerifyCommand(cmd client.Command, serverPubKey ed25519.PublicKey) error {
|
|
// Check if signature is present
|
|
if cmd.Signature == "" {
|
|
return fmt.Errorf("command missing signature")
|
|
}
|
|
|
|
// Decode the signature
|
|
sig, err := hex.DecodeString(cmd.Signature)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid signature encoding: %w", err)
|
|
}
|
|
|
|
// Verify signature length
|
|
if len(sig) != ed25519.SignatureSize {
|
|
return fmt.Errorf("invalid signature length: expected %d bytes, got %d",
|
|
ed25519.SignatureSize, len(sig))
|
|
}
|
|
|
|
// Reconstruct the signed message
|
|
message, err := v.reconstructMessage(cmd)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to reconstruct message: %w", err)
|
|
}
|
|
|
|
// Verify the Ed25519 signature
|
|
if !ed25519.Verify(serverPubKey, message, sig) {
|
|
return fmt.Errorf("signature verification failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// reconstructMessage recreates the message that was signed by the server
|
|
// This must exactly match the server's signing implementation
|
|
func (v *CommandVerifier) reconstructMessage(cmd client.Command) ([]byte, error) {
|
|
// Marshal parameters to JSON
|
|
paramsJSON, err := json.Marshal(cmd.Params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal parameters: %w", err)
|
|
}
|
|
|
|
// Create SHA256 hash of parameters
|
|
paramsHash := sha256.Sum256(paramsJSON)
|
|
paramsHashHex := hex.EncodeToString(paramsHash[:])
|
|
|
|
// Create the message in the exact format the server uses
|
|
// Format: "ID:CommandType:ParamsHash"
|
|
message := fmt.Sprintf("%s:%s:%s",
|
|
cmd.ID,
|
|
cmd.Type,
|
|
paramsHashHex)
|
|
|
|
return []byte(message), nil
|
|
}
|
|
|
|
// VerifyCommandWithTimestamp verifies a command and checks its timestamp
|
|
// This prevents replay attacks with old commands
|
|
// Note: Timestamp verification requires the CreatedAt field which is not sent to agents
|
|
// This method is kept for future enhancement when we add timestamp to the command payload
|
|
func (v *CommandVerifier) VerifyCommandWithTimestamp(
|
|
cmd client.Command,
|
|
serverPubKey ed25519.PublicKey,
|
|
maxAge time.Duration,
|
|
) error {
|
|
// First verify the signature
|
|
if err := v.VerifyCommand(cmd, serverPubKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Timestamp checking is currently disabled as CreatedAt is not included in the command sent to agents
|
|
// TODO: Add CreatedAt to command payload if timestamp verification is needed
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyCommandBatch verifies multiple commands efficiently
|
|
// This is useful when processing multiple commands at once
|
|
func (v *CommandVerifier) VerifyCommandBatch(
|
|
commands []client.Command,
|
|
serverPubKey ed25519.PublicKey,
|
|
) []error {
|
|
errors := make([]error, len(commands))
|
|
|
|
for i, cmd := range commands {
|
|
errors[i] = v.VerifyCommand(cmd, serverPubKey)
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
// ExtractCommandIDFromSignature attempts to verify a signature and returns the command ID
|
|
// This is useful for debugging and logging
|
|
func (v *CommandVerifier) ExtractCommandIDFromSignature(
|
|
signature string,
|
|
expectedMessage string,
|
|
serverPubKey ed25519.PublicKey,
|
|
) (string, error) {
|
|
// Decode signature
|
|
sig, err := hex.DecodeString(signature)
|
|
if err != nil {
|
|
return "", fmt.Errorf("invalid signature encoding: %w", err)
|
|
}
|
|
|
|
// Verify signature
|
|
if !ed25519.Verify(serverPubKey, []byte(expectedMessage), sig) {
|
|
return "", fmt.Errorf("signature verification failed")
|
|
}
|
|
|
|
// In a real implementation, we might embed the command ID in the signature
|
|
// For now, we return an empty string since the ID is part of the message
|
|
return "", nil
|
|
}
|
|
|
|
// CheckKeyRotation checks if a public key needs to be rotated
|
|
// This is a placeholder for future key rotation support
|
|
func (v *CommandVerifier) CheckKeyRotation(currentKey ed25519.PublicKey) (ed25519.PublicKey, bool, error) {
|
|
// In the future, this could:
|
|
// - Check a key rotation endpoint
|
|
// - Load multiple trusted keys
|
|
// - Implement key pinning with fallback
|
|
// - Handle emergency key revocation
|
|
|
|
// For now, just return the current key
|
|
return currentKey, false, nil
|
|
} |