Files
Redflag/aggregator-agent/internal/crypto/pubkey.go
Fimeg e6ac0b1ec4 feat: implement agent migration system
- Fix config version inflation bug in main.go
- Add dynamic subsystem checking to prevent false change detection
- Implement migration detection and execution system
- Add directory migration from /etc/aggregator to /etc/redflag
- Update all path references across codebase to use new directories
- Add configuration schema versioning and automatic migration
- Implement backup and rollback capabilities
- Add security feature detection and hardening
- Update installation scripts and sudoers for new paths
- Complete Phase 1 migration system
2025-11-04 14:25:53 -05:00

131 lines
3.7 KiB
Go

package crypto
import (
"crypto/ed25519"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
)
// getPublicKeyPath returns the platform-specific path for storing the server's public key
func getPublicKeyPath() string {
if runtime.GOOS == "windows" {
return "C:\\ProgramData\\RedFlag\\server_public_key"
}
return "/etc/redflag/server_public_key"
}
// PublicKeyResponse represents the server's public key response
type PublicKeyResponse struct {
PublicKey string `json:"public_key"`
Fingerprint string `json:"fingerprint"`
Algorithm string `json:"algorithm"`
KeySize int `json:"key_size"`
}
// FetchAndCacheServerPublicKey fetches the server's Ed25519 public key and caches it locally
// This implements Trust-On-First-Use (TOFU) security model
func FetchAndCacheServerPublicKey(serverURL string) (ed25519.PublicKey, error) {
// Check if we already have a cached key
if cachedKey, err := LoadCachedPublicKey(); err == nil && cachedKey != nil {
return cachedKey, nil
}
// Fetch from server
resp, err := http.Get(serverURL + "/api/v1/public-key")
if err != nil {
return nil, fmt.Errorf("failed to fetch public key from server: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("server returned status %d: %s", resp.StatusCode, string(body))
}
// Parse response
var keyResp PublicKeyResponse
if err := json.NewDecoder(resp.Body).Decode(&keyResp); err != nil {
return nil, fmt.Errorf("failed to parse public key response: %w", err)
}
// Validate algorithm
if keyResp.Algorithm != "ed25519" {
return nil, fmt.Errorf("unsupported signature algorithm: %s (expected ed25519)", keyResp.Algorithm)
}
// Decode hex public key
pubKeyBytes, err := hex.DecodeString(keyResp.PublicKey)
if err != nil {
return nil, fmt.Errorf("invalid public key format: %w", err)
}
if len(pubKeyBytes) != ed25519.PublicKeySize {
return nil, fmt.Errorf("invalid public key size: expected %d bytes, got %d",
ed25519.PublicKeySize, len(pubKeyBytes))
}
publicKey := ed25519.PublicKey(pubKeyBytes)
// Cache it for future use
if err := cachePublicKey(publicKey); err != nil {
// Log warning but don't fail - we have the key in memory
fmt.Printf("Warning: Failed to cache public key: %v\n", err)
}
fmt.Printf("✓ Server public key fetched and cached (fingerprint: %s)\n", keyResp.Fingerprint)
return publicKey, nil
}
// LoadCachedPublicKey loads the cached public key from disk
func LoadCachedPublicKey() (ed25519.PublicKey, error) {
keyPath := getPublicKeyPath()
data, err := os.ReadFile(keyPath)
if err != nil {
return nil, err // File doesn't exist or can't be read
}
if len(data) != ed25519.PublicKeySize {
return nil, fmt.Errorf("cached public key has invalid size: %d bytes", len(data))
}
return ed25519.PublicKey(data), nil
}
// cachePublicKey saves the public key to disk
func cachePublicKey(publicKey ed25519.PublicKey) error {
keyPath := getPublicKeyPath()
// Ensure directory exists
dir := filepath.Dir(keyPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
// Write public key (read-only for non-root users)
if err := os.WriteFile(keyPath, publicKey, 0644); err != nil {
return fmt.Errorf("failed to write public key: %w", err)
}
return nil
}
// GetPublicKey returns the cached public key or fetches it from the server
// This is the main entry point for getting the verification key
func GetPublicKey(serverURL string) (ed25519.PublicKey, error) {
// Try cached key first
if cachedKey, err := LoadCachedPublicKey(); err == nil {
return cachedKey, nil
}
// Fetch from server if not cached
return FetchAndCacheServerPublicKey(serverURL)
}