package main import ( "crypto/aes" "crypto/cipher" "encoding/hex" "fmt" "os" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" ) const ( Version = "0.1.1" Title = "Secure Editor" AlgorithmAes256Gcm = "aes-256-gcm" ) func decrypt(ciphertext, key, nonce []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } aesgcm, err := cipher.NewGCM(block) if err != nil { return nil, err } plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil) if err != nil { return nil, err } return plaintext, nil } func encrypt(plaintext, key, nonce []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } aesgcm, err := cipher.NewGCM(block) if err != nil { return nil, err } ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) return ciphertext, nil } func writeFilePreserveMode(filename string, data []byte) error { fi, err := os.Stat(filename) if err != nil { if os.IsNotExist(err) { return os.WriteFile(filename, data, 0666) } return err } err = os.WriteFile(filename, data, 0666) if err != nil { return err } return os.Chmod(filename, fi.Mode()) } func showErrorMessage(err error, parent fyne.Window) { dialog.ShowError(err, parent) } func isReadonly() bool { return os.Getenv("READONLY") == "true" } func main() { args := os.Args if len(args) == 2 && args[1] == "version" { fmt.Printf("secure-editor-go version: %s\n", Version) os.Exit(0) } if len(args) != 5 { fmt.Printf("Bad encrypt args: %v\n", args) os.Exit(1) } fileName := args[1] algorithm := args[2] if algorithm != AlgorithmAes256Gcm { fmt.Printf("Bad algorithm: %s\n", algorithm) os.Exit(1) } keyBytes, err := hex.DecodeString(args[3]) if err != nil { fmt.Printf("Bad key: %s, failed: %s\n", args[3], err.Error()) os.Exit(1) } if len(keyBytes) != 32 { fmt.Printf("Bad key length: %d\n", len(keyBytes)) os.Exit(1) } nonceBytes, err := hex.DecodeString(args[4]) if err != nil { fmt.Printf("Bad nonce: %s, failed: %s\n", args[4], err.Error()) os.Exit(1) } if len(nonceBytes) != 12 { fmt.Printf("Bad nonce length: %d\n", len(nonceBytes)) os.Exit(1) } fileData, err := os.ReadFile(fileName) if err != nil { fmt.Printf("Read file: %s, failed: %s\n", fileName, err.Error()) os.Exit(1) } plaintext, err := decrypt(fileData, keyBytes, nonceBytes) if err != nil { fmt.Printf("Decrypt file: %s, failed: %s\n", fileName, err.Error()) os.Exit(1) } isReadonly := isReadonly() secureEditorApp := app.New() window := secureEditorApp.NewWindow(Title) window.Resize(fyne.NewSize(800, 600)) window.SetOnClosed(func() { secureEditorApp.Quit() }) textEntry := widget.NewMultiLineEntry() textEntry.Wrapping = fyne.TextWrapWord initialText := string(plaintext) textEntry.SetText(initialText) if isReadonly { // textEntry.Disable() // TODO for font is grey textEntry.OnChanged = func(text string) { if text != initialText { textEntry.SetText(initialText) } } } exitButton := widget.NewButton(" [ Exit ] ", func() { secureEditorApp.Quit() }) saveButton := widget.NewButton(" [ Save ] ", func() { plaintext := textEntry.Text ciphertext, err := encrypt([]byte(plaintext), keyBytes, nonceBytes) if err != nil { showErrorMessage(err, window) return } err = writeFilePreserveMode(fileName, ciphertext) if err != nil { showErrorMessage(err, window) return } secureEditorApp.Quit() }) buttons := container.NewHBox(exitButton, saveButton) if isReadonly { buttons = container.NewHBox(exitButton) } content := container.NewBorder( nil, // top container.NewBorder(nil, nil, nil, buttons, nil), // bottom nil, // left nil, // right textEntry, // center ) window.SetContent(content) window.CenterOnScreen() window.ShowAndRun() }