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/aggregator/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) }