- 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
131 lines
3.7 KiB
Go
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)
|
|
}
|