diff --git a/CHANGELOG.md b/CHANGELOG.md index 538ee6d..c4913b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Which is based on [Keep A Changelog](http://keepachangelog.com/) - Export in a file - Use RandomString function for wallet's salt - Search is case insensite +- change space to tab for goreport ## v1.1.0 - 2019-07-23 diff --git a/README.md b/README.md index 7933d99..cea4c35 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # gpm: Go Passwords Manager [![Version](https://img.shields.io/badge/latest_version-1.1.0-green.svg)](https://git.yaegashi.fr/nishiki/gpm/releases) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://git.yaegashi.fr/nishiki/gpm/src/branch/master/LICENSE) [![Build Status](https://travis-ci.org/nishiki/gpm.svg?branch=master)](https://travis-ci.org/nishiki/gpm) +[![GoReport](https://goreportcard.com/badge/git.yaegashi.fr/nishiki/gpm)](https://goreportcard.com/report/git.yaegashi.fr/nishiki/gpm) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://git.yaegashi.fr/nishiki/gpm/src/branch/master/LICENSE) gpm is passwords manager write in go and use AES-256 to encrypt the wallets diff --git a/cmd/gpm/main.go b/cmd/gpm/main.go index f906c4e..11d11f5 100644 --- a/cmd/gpm/main.go +++ b/cmd/gpm/main.go @@ -14,10 +14,10 @@ package main -import( - "git.yaegashi.fr/nishiki/gpm/gpm" +import ( + "git.yaegashi.fr/nishiki/gpm/gpm" ) func main() { - gpm.Run() + gpm.Run() } diff --git a/gpm/cli.go b/gpm/cli.go index 229257e..f453fcb 100644 --- a/gpm/cli.go +++ b/gpm/cli.go @@ -14,294 +14,298 @@ package gpm -import( - "bufio" - "fmt" - "io/ioutil" - "os" - "strconv" - "syscall" - "github.com/atotto/clipboard" - "github.com/olekukonko/tablewriter" - "golang.org/x/crypto/ssh/terminal" +import ( + "bufio" + "fmt" + "github.com/atotto/clipboard" + "github.com/olekukonko/tablewriter" + "golang.org/x/crypto/ssh/terminal" + "io/ioutil" + "os" + "strconv" + "syscall" ) // Cli contain config and wallet to use type Cli struct { - Config Config - Wallet Wallet + Config Config + Wallet Wallet } // printEntries show entries with tables func (c *Cli) printEntries(entries []Entry) { - var otp string - var tables map[string]*tablewriter.Table + var otp string + var tables map[string]*tablewriter.Table - tables = make(map[string]*tablewriter.Table) + tables = make(map[string]*tablewriter.Table) - for i, entry := range entries { - if entry.OTP == "" { otp = "" } else { otp = "X" } - if _, present := tables[entry.Group]; present == false { - tables[entry.Group] = tablewriter.NewWriter(os.Stdout) - tables[entry.Group].SetHeader([]string{"", "Name", "URI", "User", "OTP", "Comment"}) - tables[entry.Group].SetBorder(false) - tables[entry.Group].SetColumnColor( - tablewriter.Colors{tablewriter.Normal, tablewriter.FgYellowColor}, - tablewriter.Colors{tablewriter.Normal, tablewriter.FgWhiteColor}, - tablewriter.Colors{tablewriter.Normal, tablewriter.FgCyanColor}, - tablewriter.Colors{tablewriter.Normal, tablewriter.FgGreenColor}, - tablewriter.Colors{tablewriter.Normal, tablewriter.FgWhiteColor}, - tablewriter.Colors{tablewriter.Normal, tablewriter.FgMagentaColor}) - } + for i, entry := range entries { + if entry.OTP == "" { + otp = "" + } else { + otp = "X" + } + if _, present := tables[entry.Group]; present == false { + tables[entry.Group] = tablewriter.NewWriter(os.Stdout) + tables[entry.Group].SetHeader([]string{"", "Name", "URI", "User", "OTP", "Comment"}) + tables[entry.Group].SetBorder(false) + tables[entry.Group].SetColumnColor( + tablewriter.Colors{tablewriter.Normal, tablewriter.FgYellowColor}, + tablewriter.Colors{tablewriter.Normal, tablewriter.FgWhiteColor}, + tablewriter.Colors{tablewriter.Normal, tablewriter.FgCyanColor}, + tablewriter.Colors{tablewriter.Normal, tablewriter.FgGreenColor}, + tablewriter.Colors{tablewriter.Normal, tablewriter.FgWhiteColor}, + tablewriter.Colors{tablewriter.Normal, tablewriter.FgMagentaColor}) + } - tables[entry.Group].Append([]string{ strconv.Itoa(i), entry.Name, entry.URI, entry.User, otp, entry.Comment }) - } + tables[entry.Group].Append([]string{strconv.Itoa(i), entry.Name, entry.URI, entry.User, otp, entry.Comment}) + } - for group, table := range tables { - fmt.Printf("\n%s\n\n", group) - table.Render() - fmt.Println("") - } + for group, table := range tables { + fmt.Printf("\n%s\n\n", group) + table.Render() + fmt.Println("") + } } // error print a message and exit) func (c *Cli) error(msg string) { - fmt.Printf("ERROR: %s\n", msg) - os.Exit(2) + fmt.Printf("ERROR: %s\n", msg) + os.Exit(2) } // input from the console func (c *Cli) input(text string, defaultValue string, show bool) string { - fmt.Print(text) + fmt.Print(text) - if show == false { - data, _ := terminal.ReadPassword(int(syscall.Stdin)) - text := string(data) - fmt.Printf("\n") + if show == false { + data, _ := terminal.ReadPassword(int(syscall.Stdin)) + text := string(data) + fmt.Printf("\n") - if text == "" { - return defaultValue - } - return text - } + if text == "" { + return defaultValue + } + return text + } - input := bufio.NewScanner(os.Stdin) - input.Scan() - if input.Text() == "" { - return defaultValue - } - return input.Text() + input := bufio.NewScanner(os.Stdin) + input.Scan() + if input.Text() == "" { + return defaultValue + } + return input.Text() } // selectEntry with a form func (c *Cli) selectEntry() Entry { - var index int + var index int - entries := c.Wallet.SearchEntry(*PATTERN, *GROUP) - if len(entries) == 0 { - fmt.Println("no entry found") - os.Exit(1) - } + entries := c.Wallet.SearchEntry(*PATTERN, *GROUP) + if len(entries) == 0 { + fmt.Println("no entry found") + os.Exit(1) + } - c.printEntries(entries) - if len(entries) == 1 { - return entries[0] - } + c.printEntries(entries) + if len(entries) == 1 { + return entries[0] + } - for true { - index, err := strconv.Atoi(c.input("Select the entry: ", "", true)) - if err == nil && index >= 0 && index + 1 <= len(entries) { - break - } - fmt.Println("your choice is not an integer or is out of range") - } + for true { + index, err := strconv.Atoi(c.input("Select the entry: ", "", true)) + if err == nil && index >= 0 && index+1 <= len(entries) { + break + } + fmt.Println("your choice is not an integer or is out of range") + } - return entries[index] + return entries[index] } // loadWallet get and unlock the wallet func (c *Cli) loadWallet() { - var walletName string + var walletName string - passphrase := c.input("Enter the passphrase to unlock the wallet: ", "", false) + passphrase := c.input("Enter the passphrase to unlock the wallet: ", "", false) - if *WALLET == "" { - walletName = c.Config.WalletDefault - } else { - walletName = *WALLET - } + if *WALLET == "" { + walletName = c.Config.WalletDefault + } else { + walletName = *WALLET + } - c.Wallet = Wallet{ - Name: walletName, - Path: fmt.Sprintf("%s/%s.gpm", c.Config.WalletDir, walletName), - Passphrase: passphrase, - } + c.Wallet = Wallet{ + Name: walletName, + Path: fmt.Sprintf("%s/%s.gpm", c.Config.WalletDir, walletName), + Passphrase: passphrase, + } - err := c.Wallet.Load() - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + err := c.Wallet.Load() + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } } // List the entry of a wallet func (c *Cli) listEntry() { - c.loadWallet() - entries := c.Wallet.SearchEntry(*PATTERN, *GROUP) - if len(entries) == 0 { - fmt.Println("no entry found") - os.Exit(1) - } else { - c.printEntries(entries) - } + c.loadWallet() + entries := c.Wallet.SearchEntry(*PATTERN, *GROUP) + if len(entries) == 0 { + fmt.Println("no entry found") + os.Exit(1) + } else { + c.printEntries(entries) + } } // Delete an entry of a wallet func (c *Cli) deleteEntry() { - var entry Entry + var entry Entry - c.loadWallet() - entry = c.selectEntry() - confirm := c.input("are you sure you want to remove this entry [y/N] ?", "N", true) + c.loadWallet() + entry = c.selectEntry() + confirm := c.input("are you sure you want to remove this entry [y/N] ?", "N", true) - if confirm == "y" { - err := c.Wallet.DeleteEntry(entry.ID) - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + if confirm == "y" { + err := c.Wallet.DeleteEntry(entry.ID) + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } - err = c.Wallet.Save() - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + err = c.Wallet.Save() + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } - fmt.Println("the entry has been deleted") - } + fmt.Println("the entry has been deleted") + } } // Add a new entry in wallet func (c *Cli) addEntry() { - c.loadWallet() + c.loadWallet() - entry := Entry{} - entry.GenerateID() - entry.Name = c.input("Enter the name: ", "", true) - entry.Group = c.input("Enter the group: ", "", true) - entry.URI = c.input("Enter the URI: ", "", true) - entry.User = c.input("Enter the username: ", "", true) - if *RANDOM { - entry.Password = RandomString(c.Config.PasswordLength, - c.Config.PasswordLetter, c.Config.PasswordDigit, c.Config.PasswordSpecial) - } else { - entry.Password = c.input("Enter the new password: ", entry.Password, false) - } - entry.OTP = c.input("Enter the OTP key: ", "", false) - entry.Comment = c.input("Enter a comment: ", "", true) + entry := Entry{} + entry.GenerateID() + entry.Name = c.input("Enter the name: ", "", true) + entry.Group = c.input("Enter the group: ", "", true) + entry.URI = c.input("Enter the URI: ", "", true) + entry.User = c.input("Enter the username: ", "", true) + if *RANDOM { + entry.Password = RandomString(c.Config.PasswordLength, + c.Config.PasswordLetter, c.Config.PasswordDigit, c.Config.PasswordSpecial) + } else { + entry.Password = c.input("Enter the new password: ", entry.Password, false) + } + entry.OTP = c.input("Enter the OTP key: ", "", false) + entry.Comment = c.input("Enter a comment: ", "", true) - err := c.Wallet.AddEntry(entry) - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + err := c.Wallet.AddEntry(entry) + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } - err = c.Wallet.Save() - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + err = c.Wallet.Save() + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } - fmt.Println("the entry has been added") + fmt.Println("the entry has been added") } // Update an entry in wallet func (c *Cli) updateEntry() { - c.loadWallet() + c.loadWallet() - entry := c.selectEntry() - entry.Name = c.input("Enter the new name: ", entry.Name, true) - entry.Group = c.input("Enter the new group: ", entry.Group, true) - entry.URI = c.input("Enter the new URI: ", entry.URI, true) - entry.User = c.input("Enter the new username: ", entry.User, true) - if *RANDOM { - entry.Password = RandomString(c.Config.PasswordLength, - c.Config.PasswordLetter, c.Config.PasswordDigit, c.Config.PasswordSpecial) - } else { - entry.Password = c.input("Enter the new password: ", entry.Password, false) - } - entry.OTP = c.input("Enter the new OTP key: ", entry.OTP, false) - entry.Comment = c.input("Enter a new comment: ", entry.Comment, true) + entry := c.selectEntry() + entry.Name = c.input("Enter the new name: ", entry.Name, true) + entry.Group = c.input("Enter the new group: ", entry.Group, true) + entry.URI = c.input("Enter the new URI: ", entry.URI, true) + entry.User = c.input("Enter the new username: ", entry.User, true) + if *RANDOM { + entry.Password = RandomString(c.Config.PasswordLength, + c.Config.PasswordLetter, c.Config.PasswordDigit, c.Config.PasswordSpecial) + } else { + entry.Password = c.input("Enter the new password: ", entry.Password, false) + } + entry.OTP = c.input("Enter the new OTP key: ", entry.OTP, false) + entry.Comment = c.input("Enter a new comment: ", entry.Comment, true) - err := c.Wallet.UpdateEntry(entry) - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } - c.Wallet.Save() + err := c.Wallet.UpdateEntry(entry) + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } + c.Wallet.Save() } // Copy login and password from an entry func (c *Cli) copyEntry() { - c.loadWallet() - entry := c.selectEntry() + c.loadWallet() + entry := c.selectEntry() - for true { - choice := c.input("select one action: ", "", true) - switch choice { - case "l": - clipboard.WriteAll(entry.User) - case "p": - clipboard.WriteAll(entry.Password) - case "o": - code, time, _ := entry.OTPCode() - fmt.Printf("this OTP code is available for %d seconds\n", time) - clipboard.WriteAll(code) - case "q": - os.Exit(0) - default: - fmt.Println("l -> copy login") - fmt.Println("p -> copy password") - fmt.Println("o -> copy OTP code") - fmt.Println("q -> quit") - } - } + for true { + choice := c.input("select one action: ", "", true) + switch choice { + case "l": + clipboard.WriteAll(entry.User) + case "p": + clipboard.WriteAll(entry.Password) + case "o": + code, time, _ := entry.OTPCode() + fmt.Printf("this OTP code is available for %d seconds\n", time) + clipboard.WriteAll(code) + case "q": + os.Exit(0) + default: + fmt.Println("l -> copy login") + fmt.Println("p -> copy password") + fmt.Println("o -> copy OTP code") + fmt.Println("q -> quit") + } + } } // Import entries from json file func (c *Cli) ImportWallet() { - c.loadWallet() + c.loadWallet() - _, err := os.Stat(*IMPORT) - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + _, err := os.Stat(*IMPORT) + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } - data, err := ioutil.ReadFile(*IMPORT) - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + data, err := ioutil.ReadFile(*IMPORT) + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } - err = c.Wallet.Import(data) - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + err = c.Wallet.Import(data) + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } - err = c.Wallet.Save() - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + err = c.Wallet.Save() + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } - fmt.Println("the import was successful") + fmt.Println("the import was successful") } // Export a wallet in json format func (c *Cli) ExportWallet() { - c.loadWallet() + c.loadWallet() - data, err := c.Wallet.Export() - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + data, err := c.Wallet.Export() + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } - err = ioutil.WriteFile(*EXPORT, data, 0600) - if err != nil { - c.error(fmt.Sprintf("%s", err)) - } + err = ioutil.WriteFile(*EXPORT, data, 0600) + if err != nil { + c.error(fmt.Sprintf("%s", err)) + } - fmt.Println("the export was successful") + fmt.Println("the export was successful") } diff --git a/gpm/config.go b/gpm/config.go index b075f9d..69188f9 100644 --- a/gpm/config.go +++ b/gpm/config.go @@ -15,90 +15,90 @@ package gpm import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "os/user" - "runtime" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/user" + "runtime" ) // Config struct contain the config type Config struct { - WalletDir string `json:"wallet_dir"` - WalletDefault string `json:"wallet_default"` - PasswordLength int `json:"password_length"` - PasswordLetter bool `json:"password_letter"` - PasswordDigit bool `json:"password_digit"` - PasswordSpecial bool `json:"password_special"` + WalletDir string `json:"wallet_dir"` + WalletDefault string `json:"wallet_default"` + PasswordLength int `json:"password_length"` + PasswordLetter bool `json:"password_letter"` + PasswordDigit bool `json:"password_digit"` + PasswordSpecial bool `json:"password_special"` } // Init the configuration func (c *Config) Init() error { - user, err := user.Current() - if err != nil { - return err - } + user, err := user.Current() + if err != nil { + return err + } - if runtime.GOOS == "darwin" { - c.WalletDir = fmt.Sprintf("%s/Library/Preferences/gpm", user.HomeDir) - } else if runtime.GOOS == "windows" { - c.WalletDir = fmt.Sprintf("%s/AppData/Local/gpm", user.HomeDir) - } else { - c.WalletDir = fmt.Sprintf("%s/.config/gpm", user.HomeDir) - } - c.WalletDefault = "default" - c.PasswordLength = 16 - c.PasswordLetter = true - c.PasswordDigit = true - c.PasswordSpecial = false + if runtime.GOOS == "darwin" { + c.WalletDir = fmt.Sprintf("%s/Library/Preferences/gpm", user.HomeDir) + } else if runtime.GOOS == "windows" { + c.WalletDir = fmt.Sprintf("%s/AppData/Local/gpm", user.HomeDir) + } else { + c.WalletDir = fmt.Sprintf("%s/.config/gpm", user.HomeDir) + } + c.WalletDefault = "default" + c.PasswordLength = 16 + c.PasswordLetter = true + c.PasswordDigit = true + c.PasswordSpecial = false - return nil + return nil } // Load the configuration from a file func (c *Config) Load(path string) error { - err := c.Init() - if err != nil { - return err - } + err := c.Init() + if err != nil { + return err + } - if path != "" { - _, err = os.Stat(path) - if err != nil { + if path != "" { + _, err = os.Stat(path) + if err != nil { - } + } - data, err := ioutil.ReadFile(path) - if err != nil { - return err - } + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } - err = json.Unmarshal(data, &c) - if err != nil { - return err - } - } + err = json.Unmarshal(data, &c) + if err != nil { + return err + } + } - err = os.MkdirAll(c.WalletDir, 0700) - if err != nil { - return err - } + err = os.MkdirAll(c.WalletDir, 0700) + if err != nil { + return err + } - return nil + return nil } // Save the configuration func (c *Config) Save(path string) error { - data, err := json.Marshal(&c) - if err != nil { - return err - } + data, err := json.Marshal(&c) + if err != nil { + return err + } - err = ioutil.WriteFile(path, []byte(data), 0644) - if err != nil { - return err - } + err = ioutil.WriteFile(path, []byte(data), 0644) + if err != nil { + return err + } - return nil + return nil } diff --git a/gpm/config_test.go b/gpm/config_test.go index 9b9d409..124366c 100644 --- a/gpm/config_test.go +++ b/gpm/config_test.go @@ -1,72 +1,72 @@ package gpm import ( - "io/ioutil" - "os" - "testing" + "io/ioutil" + "os" + "testing" ) func TestInit(t *testing.T) { - var config Config + var config Config - err := config.Init() - if err != nil { - t.Error("the config init mustn't return an error") - } + err := config.Init() + if err != nil { + t.Error("the config init mustn't return an error") + } - if config.WalletDefault != "default" { - t.Errorf("the WalletDefaut must be 'default': %s", config.WalletDefault) - } + if config.WalletDefault != "default" { + t.Errorf("the WalletDefaut must be 'default': %s", config.WalletDefault) + } - if config.PasswordLength != 16 { - t.Errorf("the PasswordLength must be 16: %d", config.PasswordLength) - } + if config.PasswordLength != 16 { + t.Errorf("the PasswordLength must be 16: %d", config.PasswordLength) + } - if config.PasswordLetter != true { - t.Error("the PasswordLetter must be true") - } + if config.PasswordLetter != true { + t.Error("the PasswordLetter must be true") + } - if config.PasswordDigit != true { - t.Error("the PasswordDigit must be true") - } + if config.PasswordDigit != true { + t.Error("the PasswordDigit must be true") + } - if config.PasswordSpecial != false { - t.Error("the PasswordSpecial must be false") - } + if config.PasswordSpecial != false { + t.Error("the PasswordSpecial must be false") + } } func TestSave(t *testing.T) { - var config Config + var config Config - tmpFile, _ := ioutil.TempFile(os.TempDir(), "gpm_test-") - defer os.Remove(tmpFile.Name()) + tmpFile, _ := ioutil.TempFile(os.TempDir(), "gpm_test-") + defer os.Remove(tmpFile.Name()) - config.Init() - err := config.Save(tmpFile.Name()) - if err != nil { - t.Errorf("save config mustn't return an error: %s", err) - } + config.Init() + err := config.Save(tmpFile.Name()) + if err != nil { + t.Errorf("save config mustn't return an error: %s", err) + } } func TestLoadWithFile(t *testing.T) { - var config Config + var config Config - tmpFile, _ := ioutil.TempFile(os.TempDir(), "gpm_test-") - defer os.Remove(tmpFile.Name()) + tmpFile, _ := ioutil.TempFile(os.TempDir(), "gpm_test-") + defer os.Remove(tmpFile.Name()) - config.Init() - config.Save(tmpFile.Name()) - err := config.Load(tmpFile.Name()) - if err != nil { - t.Errorf("load config with file mustn't return an error: %s", err) - } + config.Init() + config.Save(tmpFile.Name()) + err := config.Load(tmpFile.Name()) + if err != nil { + t.Errorf("load config with file mustn't return an error: %s", err) + } } func TestLoadWithoutFile(t *testing.T) { - var config Config + var config Config - err := config.Load("") - if err != nil { - t.Errorf("load config without file mustn't return an error: %s", err) - } + err := config.Load("") + if err != nil { + t.Errorf("load config without file mustn't return an error: %s", err) + } } diff --git a/gpm/crypto.go b/gpm/crypto.go index 28e16de..c84c73b 100644 --- a/gpm/crypto.go +++ b/gpm/crypto.go @@ -14,71 +14,71 @@ package gpm -import( - "crypto/aes" - "crypto/sha512" - "crypto/cipher" - "crypto/rand" - "encoding/base64" - "io" - mrand "math/rand" - "time" +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "encoding/base64" + "io" + mrand "math/rand" + "time" - "golang.org/x/crypto/pbkdf2" + "golang.org/x/crypto/pbkdf2" ) // Encrypt data with aes256 func Encrypt(data []byte, passphrase string, salt string) (string, error) { - key := pbkdf2.Key([]byte(passphrase), []byte(salt), 4096, 32, sha512.New) + key := pbkdf2.Key([]byte(passphrase), []byte(salt), 4096, 32, sha512.New) - block, err := aes.NewCipher([]byte(key)) - if err != nil { - return "", err - } + block, err := aes.NewCipher([]byte(key)) + if err != nil { + return "", err + } - cipher, err := cipher.NewGCM(block) - if err != nil { - return "", err - } + cipher, err := cipher.NewGCM(block) + if err != nil { + return "", err + } - nonce := make([]byte, cipher.NonceSize()) - _, err = io.ReadFull(rand.Reader, nonce) - if err != nil { - return "", err - } + nonce := make([]byte, cipher.NonceSize()) + _, err = io.ReadFull(rand.Reader, nonce) + if err != nil { + return "", err + } - dataEncrypted := cipher.Seal(nonce, nonce, data, nil) + dataEncrypted := cipher.Seal(nonce, nonce, data, nil) - return base64.StdEncoding.EncodeToString(dataEncrypted), nil + return base64.StdEncoding.EncodeToString(dataEncrypted), nil } // Decrypt data func Decrypt(data string, passphrase string, salt string) ([]byte, error) { - key := pbkdf2.Key([]byte(passphrase), []byte(salt), 4096, 32, sha512.New) + key := pbkdf2.Key([]byte(passphrase), []byte(salt), 4096, 32, sha512.New) - rawData, err := base64.StdEncoding.DecodeString(data) - if err != nil { - return []byte{}, err - } + rawData, err := base64.StdEncoding.DecodeString(data) + if err != nil { + return []byte{}, err + } - block, err := aes.NewCipher([]byte(key)) - if err != nil { - return []byte{}, err - } + block, err := aes.NewCipher([]byte(key)) + if err != nil { + return []byte{}, err + } - cipher, err := cipher.NewGCM(block) - if err != nil { - return []byte{}, err - } + cipher, err := cipher.NewGCM(block) + if err != nil { + return []byte{}, err + } - nonceSize := cipher.NonceSize() - nonce, text := rawData[:nonceSize], rawData[nonceSize:] - plaintext, err := cipher.Open(nil, nonce, text, nil) - if err != nil { - return []byte{}, err - } + nonceSize := cipher.NonceSize() + nonce, text := rawData[:nonceSize], rawData[nonceSize:] + plaintext, err := cipher.Open(nil, nonce, text, nil) + if err != nil { + return []byte{}, err + } - return plaintext, nil + return plaintext, nil } // RandomString generate a random string @@ -86,20 +86,26 @@ func RandomString(length int, letter bool, digit bool, special bool) string { digits := "0123456789" specials := "~=+%^*/()[]{}/!@#$?|" letters := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - chars := "" + chars := "" randomString := make([]byte, length) - if letter { chars = chars + letters } - if digit { chars = chars + digits } - if special { chars = chars + specials } - if !letter && !digit && !special { - chars = digits + letters - } - - mrand.Seed(time.Now().UnixNano()) - for i := 0; i < length; i++ { - randomString[i] = chars[mrand.Intn(len(chars))] + if letter { + chars = chars + letters + } + if digit { + chars = chars + digits + } + if special { + chars = chars + specials + } + if !letter && !digit && !special { + chars = digits + letters } - return string(randomString) + mrand.Seed(time.Now().UnixNano()) + for i := 0; i < length; i++ { + randomString[i] = chars[mrand.Intn(len(chars))] + } + + return string(randomString) } diff --git a/gpm/crypto_test.go b/gpm/crypto_test.go index 8bfb014..83e8f88 100644 --- a/gpm/crypto_test.go +++ b/gpm/crypto_test.go @@ -1,91 +1,91 @@ package gpm import ( - "regexp" - "testing" + "regexp" + "testing" ) func TestEncrypt(t *testing.T) { - secret := []byte("secret data") + secret := []byte("secret data") - data, err := Encrypt(secret, "passphrase", "salt") - if err != nil { - t.Errorf("Encrypt mustn't return an error: %s", err) + data, err := Encrypt(secret, "passphrase", "salt") + if err != nil { + t.Errorf("Encrypt mustn't return an error: %s", err) - } - if data == "" { - t.Error("Encrypt must generate a string not empty") - } + } + if data == "" { + t.Error("Encrypt must generate a string not empty") + } } func TestDecrypt(t *testing.T) { - secret := "secret data" + secret := "secret data" - dataEncrypted, _ := Encrypt([]byte(secret), "passphrase", "salt") - data, err := Decrypt(dataEncrypted, "passphrase", "salt") - if err != nil { - t.Errorf("Decrypt mustn't return an error: %s", err) - } - if string(data) != secret { - t.Errorf("the encrypted secret is different of decrypted secret: %s", data) - } + dataEncrypted, _ := Encrypt([]byte(secret), "passphrase", "salt") + data, err := Decrypt(dataEncrypted, "passphrase", "salt") + if err != nil { + t.Errorf("Decrypt mustn't return an error: %s", err) + } + if string(data) != secret { + t.Errorf("the encrypted secret is different of decrypted secret: %s", data) + } } func TestDecryptWithBadPassphrase(t *testing.T) { - secret := []byte("secret data") + secret := []byte("secret data") - dataEncrypted, _ := Encrypt(secret, "passphrase", "salt") - _, err := Decrypt(dataEncrypted, "bad", "salt") - if err == nil { - t.Error("Decrypt must return an error with bad passphrase") - } + dataEncrypted, _ := Encrypt(secret, "passphrase", "salt") + _, err := Decrypt(dataEncrypted, "bad", "salt") + if err == nil { + t.Error("Decrypt must return an error with bad passphrase") + } } func TestDecryptWithBadSalt(t *testing.T) { - secret := []byte("secret data") + secret := []byte("secret data") - dataEncrypted, _ := Encrypt(secret, "passphrase", "salt") - _, err := Decrypt(dataEncrypted, "passphrase", "bad") - if err == nil { - t.Error("Decrypt must return an error with bad salt") - } + dataEncrypted, _ := Encrypt(secret, "passphrase", "salt") + _, err := Decrypt(dataEncrypted, "passphrase", "bad") + if err == nil { + t.Error("Decrypt must return an error with bad salt") + } } func TestRandomStringLength(t *testing.T) { - password := RandomString(64, false, false, false) - if len(password) != 64 { - t.Errorf("the string must have 64 chars: %d", len(password)) - } - r := regexp.MustCompile(`^[a-zA-Z0-9]{64}$`) - match := r.FindSubmatch([]byte(password)) - if len(match) == 0 { - t.Errorf("the string must contain only digit and alphabetic characters: %s", password) - } + password := RandomString(64, false, false, false) + if len(password) != 64 { + t.Errorf("the string must have 64 chars: %d", len(password)) + } + r := regexp.MustCompile(`^[a-zA-Z0-9]{64}$`) + match := r.FindSubmatch([]byte(password)) + if len(match) == 0 { + t.Errorf("the string must contain only digit and alphabetic characters: %s", password) + } } func TestRandomStringOnlyDigit(t *testing.T) { - password := RandomString(64, false, true, false) - r := regexp.MustCompile(`^[0-9]{64}$`) - match := r.FindSubmatch([]byte(password)) - if len(match) == 0 { - t.Errorf("the string must contain only digit characters: %s", password) - } + password := RandomString(64, false, true, false) + r := regexp.MustCompile(`^[0-9]{64}$`) + match := r.FindSubmatch([]byte(password)) + if len(match) == 0 { + t.Errorf("the string must contain only digit characters: %s", password) + } } func TestRandomStringOnlyAlphabetic(t *testing.T) { - password := RandomString(64, true, false, false) - r := regexp.MustCompile(`^[a-zA-Z]{64}$`) - match := r.FindSubmatch([]byte(password)) - if len(match) == 0 { - t.Errorf("the string must contain only alphabetic characters: %s", password) - } + password := RandomString(64, true, false, false) + r := regexp.MustCompile(`^[a-zA-Z]{64}$`) + match := r.FindSubmatch([]byte(password)) + if len(match) == 0 { + t.Errorf("the string must contain only alphabetic characters: %s", password) + } } func TestRandomStringOnlySpecial(t *testing.T) { - password := RandomString(64, false, false, true) - r := regexp.MustCompile(`^[\~\=\+\%\^\*\/\(\)\[\]\{\}\!\@\#\$\?\|]{64}$`) - match := r.FindSubmatch([]byte(password)) - if len(match) == 0 { - t.Errorf("the string must contain only alphabetic characters: %s", password) - } + password := RandomString(64, false, false, true) + r := regexp.MustCompile(`^[\~\=\+\%\^\*\/\(\)\[\]\{\}\!\@\#\$\?\|]{64}$`) + match := r.FindSubmatch([]byte(password)) + if len(match) == 0 { + t.Errorf("the string must contain only alphabetic characters: %s", password) + } } diff --git a/gpm/entry.go b/gpm/entry.go index 8e382c0..d3b0b8b 100644 --- a/gpm/entry.go +++ b/gpm/entry.go @@ -14,58 +14,58 @@ package gpm -import( - "fmt" - "time" - "net/url" +import ( + "fmt" + "net/url" + "time" - "github.com/pquerna/otp/totp" + "github.com/pquerna/otp/totp" ) // Entry struct have the password informations type Entry struct { - Name string - ID string - URI string - User string - Password string - OTP string - Group string - Comment string - Create int64 - LastUpdate int64 + Name string + ID string + URI string + User string + Password string + OTP string + Group string + Comment string + Create int64 + LastUpdate int64 } // Verify if the item have'nt error func (e *Entry) Verify() error { - if e.ID == "" { - return fmt.Errorf("you must generate an ID") - } + if e.ID == "" { + return fmt.Errorf("you must generate an ID") + } - if e.Name == "" { - return fmt.Errorf("you must define a name") - } + if e.Name == "" { + return fmt.Errorf("you must define a name") + } - uri, _ := url.Parse(e.URI) - if e.URI != "" && uri.Host == "" { - return fmt.Errorf("the uri isn't a valid uri") - } + uri, _ := url.Parse(e.URI) + if e.URI != "" && uri.Host == "" { + return fmt.Errorf("the uri isn't a valid uri") + } - return nil + return nil } // GenerateID create a new id for the entry func (e *Entry) GenerateID() { - e.ID = fmt.Sprintf("%d", time.Now().UnixNano()) + e.ID = fmt.Sprintf("%d", time.Now().UnixNano()) } // OTPCode generate an OTP Code -func (e *Entry) OTPCode() (string, int64, error){ - code, err := totp.GenerateCode(e.OTP, time.Now()) - time := 30 - (time.Now().Unix() % 30) - if err != nil { - return "", 0, err - } +func (e *Entry) OTPCode() (string, int64, error) { + code, err := totp.GenerateCode(e.OTP, time.Now()) + time := 30 - (time.Now().Unix() % 30) + if err != nil { + return "", 0, err + } - return code, time, nil + return code, time, nil } diff --git a/gpm/entry_test.go b/gpm/entry_test.go index d2cc668..1776d55 100644 --- a/gpm/entry_test.go +++ b/gpm/entry_test.go @@ -3,63 +3,63 @@ package gpm import "testing" func TestCreateEmptyEntry(t *testing.T) { - var entry Entry - err := entry.Verify() - if err == nil { - t.Error("an entry Without an ID must return an error") - } + var entry Entry + err := entry.Verify() + if err == nil { + t.Error("an entry Without an ID must return an error") + } } func TestCreateEntryWithoutName(t *testing.T) { - var entry Entry - entry.GenerateID() - if entry.ID == "" { - t.Error("generateID can't be generate a void ID") - } + var entry Entry + entry.GenerateID() + if entry.ID == "" { + t.Error("generateID can't be generate a void ID") + } - err := entry.Verify() - if err == nil { - t.Error("an entry without a name must return an error") - } + err := entry.Verify() + if err == nil { + t.Error("an entry without a name must return an error") + } } func TestCreateEntryWithName(t *testing.T) { - entry := Entry{ Name: "test" } - entry.GenerateID() - err := entry.Verify() - if err != nil { - t.Errorf("an entry with a name mustn't return an error: %s", err) - } + entry := Entry{Name: "test"} + entry.GenerateID() + err := entry.Verify() + if err != nil { + t.Errorf("an entry with a name mustn't return an error: %s", err) + } } func TestCreateEntryWithBadURI(t *testing.T) { - entry := Entry{ Name: "test", URI: "url/bad:" } - entry.GenerateID() - err := entry.Verify() - if err == nil { - t.Error("an entry with a bad URI must return an error") - } + entry := Entry{Name: "test", URI: "url/bad:"} + entry.GenerateID() + err := entry.Verify() + if err == nil { + t.Error("an entry with a bad URI must return an error") + } } func TestCreateEntryWithGoodURI(t *testing.T) { - entry := Entry{ Name: "test", URI: "http://localhost:8081" } - entry.GenerateID() - err := entry.Verify() - if err != nil { - t.Errorf("an entry with a good URI mustn't return an error: %s", err) - } + entry := Entry{Name: "test", URI: "http://localhost:8081"} + entry.GenerateID() + err := entry.Verify() + if err != nil { + t.Errorf("an entry with a good URI mustn't return an error: %s", err) + } } func TestGenerateOTPCode(t *testing.T) { - entry := Entry{ OTP: "JBSWY3DPEHPK3PXP" } - code, time, err := entry.OTPCode() - if err != nil { - t.Errorf("must generate an OTP code without error: %s", err) - } - if len(code) != 6 { - t.Errorf("must generate an OTP code with 6 chars: %s", code) - } - if time < 0 || time > 30 { - t.Errorf("time must be between 0 and 30: %d", time) - } + entry := Entry{OTP: "JBSWY3DPEHPK3PXP"} + code, time, err := entry.OTPCode() + if err != nil { + t.Errorf("must generate an OTP code without error: %s", err) + } + if len(code) != 6 { + t.Errorf("must generate an OTP code with 6 chars: %s", code) + } + if time < 0 || time > 30 { + t.Errorf("time must be between 0 and 30: %d", time) + } } diff --git a/gpm/main.go b/gpm/main.go index 0f80dc3..7c09741 100644 --- a/gpm/main.go +++ b/gpm/main.go @@ -14,58 +14,58 @@ package gpm -import( - "fmt" - "flag" - "os" +import ( + "flag" + "fmt" + "os" ) // Options -var( - ADD = flag.Bool("add", false, "add a new entry in the wallet") - UPDATE = flag.Bool("update", false, "update an entry") - DELETE = flag.Bool("delete", false, "delete an entry") - LIST = flag.Bool("list", false, "list the entries in a wallet") - LENGTH = flag.Int("length", 16, "specify the password length") - COPY = flag.Bool("copy", false, "enter an copy mode for an entry") - CONFIG = flag.String("config", "", "specify the config file") - GROUP = flag.String("group", "", "search the entries in this group ") - WALLET = flag.String("wallet", "", "specify the wallet") - PATTERN = flag.String("pattern", "", "search the entries with this pattern") - RANDOM = flag.Bool("random", false, "generate a random password for a new entry or an update") - PASSWD = flag.Bool("password", false, "generate and print a random password") - DIGIT = flag.Bool("digit", false, "use digit to generate a random password") - LETTER = flag.Bool("letter", false, "use letter to generate a random password") - SPECIAL = flag.Bool("special", false, "use special chars to generate a random password") - EXPORT = flag.String("export", "", "json file path to export a wallet") - IMPORT = flag.String("import", "", "json file path to import entries") - HELP = flag.Bool("help", false, "print this help message") +var ( + ADD = flag.Bool("add", false, "add a new entry in the wallet") + UPDATE = flag.Bool("update", false, "update an entry") + DELETE = flag.Bool("delete", false, "delete an entry") + LIST = flag.Bool("list", false, "list the entries in a wallet") + LENGTH = flag.Int("length", 16, "specify the password length") + COPY = flag.Bool("copy", false, "enter an copy mode for an entry") + CONFIG = flag.String("config", "", "specify the config file") + GROUP = flag.String("group", "", "search the entries in this group ") + WALLET = flag.String("wallet", "", "specify the wallet") + PATTERN = flag.String("pattern", "", "search the entries with this pattern") + RANDOM = flag.Bool("random", false, "generate a random password for a new entry or an update") + PASSWD = flag.Bool("password", false, "generate and print a random password") + DIGIT = flag.Bool("digit", false, "use digit to generate a random password") + LETTER = flag.Bool("letter", false, "use letter to generate a random password") + SPECIAL = flag.Bool("special", false, "use special chars to generate a random password") + EXPORT = flag.String("export", "", "json file path to export a wallet") + IMPORT = flag.String("import", "", "json file path to import entries") + HELP = flag.Bool("help", false, "print this help message") ) // Run the cli interface func Run() { - var cli Cli - cli.Config.Load(*CONFIG) + var cli Cli + cli.Config.Load(*CONFIG) - flag.Parse() - if *HELP { - flag.PrintDefaults() - os.Exit(1) - } else if *PASSWD { - fmt.Println(RandomString(*LENGTH, *LETTER, *DIGIT, *SPECIAL)) - } else if *LIST { - cli.listEntry() - } else if *COPY { - cli.copyEntry() - } else if *ADD { - cli.addEntry() - } else if *UPDATE { - cli.updateEntry() - } else if *DELETE { - cli.deleteEntry() - } else if *IMPORT != "" { - cli.ImportWallet() - } else if *EXPORT != "" { - cli.ExportWallet() - } + flag.Parse() + if *HELP { + flag.PrintDefaults() + os.Exit(1) + } else if *PASSWD { + fmt.Println(RandomString(*LENGTH, *LETTER, *DIGIT, *SPECIAL)) + } else if *LIST { + cli.listEntry() + } else if *COPY { + cli.copyEntry() + } else if *ADD { + cli.addEntry() + } else if *UPDATE { + cli.updateEntry() + } else if *DELETE { + cli.deleteEntry() + } else if *IMPORT != "" { + cli.ImportWallet() + } else if *EXPORT != "" { + cli.ExportWallet() + } } diff --git a/gpm/wallet.go b/gpm/wallet.go index a3ce661..6becff6 100644 --- a/gpm/wallet.go +++ b/gpm/wallet.go @@ -15,206 +15,205 @@ package gpm import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "regexp" - "time" - "sort" - "strings" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "regexp" + "sort" + "strings" + "time" ) - // WalletFile contains the data in file type WalletFile struct { - Salt string - Data string + Salt string + Data string } // Wallet struct have wallet informations type Wallet struct { - Name string - Path string - Salt string - Passphrase string - Entries []Entry + Name string + Path string + Salt string + Passphrase string + Entries []Entry } // Load all wallet's Entrys from the disk func (w *Wallet) Load() error { - var walletFile WalletFile + var walletFile WalletFile - _, err := os.Stat(w.Path) - if err != nil { - return nil - } + _, err := os.Stat(w.Path) + if err != nil { + return nil + } - content, err := ioutil.ReadFile(w.Path) - if err != nil { - return err - } + content, err := ioutil.ReadFile(w.Path) + if err != nil { + return err + } - err = json.Unmarshal(content, &walletFile) - if err != nil { - return err - } + err = json.Unmarshal(content, &walletFile) + if err != nil { + return err + } - w.Salt = walletFile.Salt - data, err := Decrypt(string(walletFile.Data), w.Passphrase, w.Salt) - if err != nil { - return err - } + w.Salt = walletFile.Salt + data, err := Decrypt(string(walletFile.Data), w.Passphrase, w.Salt) + if err != nil { + return err + } - err = json.Unmarshal(data, &w.Entries) - if err != nil { - return err - } + err = json.Unmarshal(data, &w.Entries) + if err != nil { + return err + } - return nil + return nil } // Save the wallet on the disk func (w *Wallet) Save() error { - if w.Salt == "" { - w.Salt = RandomString(12, true, true, false) - } + if w.Salt == "" { + w.Salt = RandomString(12, true, true, false) + } - data, err := json.Marshal(&w.Entries) - if err != nil { - return err - } + data, err := json.Marshal(&w.Entries) + if err != nil { + return err + } - dataEncrypted, err := Encrypt(data, w.Passphrase, w.Salt) - if err != nil { - return err - } + dataEncrypted, err := Encrypt(data, w.Passphrase, w.Salt) + if err != nil { + return err + } - walletFile := WalletFile{ Salt: w.Salt, Data: dataEncrypted } - content, err := json.Marshal(&walletFile) - if err != nil { - return err - } + walletFile := WalletFile{Salt: w.Salt, Data: dataEncrypted} + content, err := json.Marshal(&walletFile) + if err != nil { + return err + } - err = ioutil.WriteFile(w.Path, content, 0600) - if err != nil { - return err - } + err = ioutil.WriteFile(w.Path, content, 0600) + if err != nil { + return err + } - return nil + return nil } // SearchEntry return an array with the array expected with the pattern func (w *Wallet) SearchEntry(pattern string, group string) []Entry { - var entries []Entry - r := regexp.MustCompile(strings.ToLower(pattern)) + var entries []Entry + r := regexp.MustCompile(strings.ToLower(pattern)) - for _, entry := range w.Entries { - if group != "" && strings.ToLower(entry.Group) != strings.ToLower(group) { - continue - } - if r.Match([]byte(strings.ToLower(entry.Name))) || - r.Match([]byte(strings.ToLower(entry.Comment))) || r.Match([]byte(strings.ToLower(entry.URI))) { - entries = append(entries, entry) - } - } + for _, entry := range w.Entries { + if group != "" && strings.ToLower(entry.Group) != strings.ToLower(group) { + continue + } + if r.Match([]byte(strings.ToLower(entry.Name))) || + r.Match([]byte(strings.ToLower(entry.Comment))) || r.Match([]byte(strings.ToLower(entry.URI))) { + entries = append(entries, entry) + } + } - sort.Slice(entries, func(i, j int) bool { - return entries[i].Group < entries[j].Group - }) + sort.Slice(entries, func(i, j int) bool { + return entries[i].Group < entries[j].Group + }) - return entries + return entries } // SearchEntryByID return an Entry func (w *Wallet) SearchEntryByID(id string) Entry { - for _, entry := range w.Entries { - if entry.ID == id { - return entry - } - } + for _, entry := range w.Entries { + if entry.ID == id { + return entry + } + } - return Entry{} + return Entry{} } // AddEntry append a new entry to wallet func (w *Wallet) AddEntry(entry Entry) error { - err := entry.Verify() - if err != nil { - return err - } + err := entry.Verify() + if err != nil { + return err + } - if w.SearchEntryByID(entry.ID) != (Entry{}) { - return fmt.Errorf("the id already exists in wallet, can't add the entry") - } + if w.SearchEntryByID(entry.ID) != (Entry{}) { + return fmt.Errorf("the id already exists in wallet, can't add the entry") + } - entry.Create = time.Now().Unix() - entry.LastUpdate = entry.Create - w.Entries = append(w.Entries, entry) + entry.Create = time.Now().Unix() + entry.LastUpdate = entry.Create + w.Entries = append(w.Entries, entry) - return nil + return nil } // DeleteEntry delete an entry to wallet func (w *Wallet) DeleteEntry(id string) error { - for index, entry := range w.Entries { - if entry.ID == id { - w.Entries = append(w.Entries[:index], w.Entries[index+1:]...) - return nil - } - } + for index, entry := range w.Entries { + if entry.ID == id { + w.Entries = append(w.Entries[:index], w.Entries[index+1:]...) + return nil + } + } - return fmt.Errorf("entry not found with this id") + return fmt.Errorf("entry not found with this id") } // UpdateEntry update an Entry to wallet func (w *Wallet) UpdateEntry(entry Entry) error { - oldEntry := w.SearchEntryByID(entry.ID) - if oldEntry == (Entry{}) { - return fmt.Errorf("entry not found with this id") - } + oldEntry := w.SearchEntryByID(entry.ID) + if oldEntry == (Entry{}) { + return fmt.Errorf("entry not found with this id") + } - err := entry.Verify() - if err != nil { - return err - } + err := entry.Verify() + if err != nil { + return err + } - entry.LastUpdate = time.Now().Unix() - for index, i := range w.Entries { - if entry.ID == i.ID { - w.Entries[index] = entry - return nil - } - } + entry.LastUpdate = time.Now().Unix() + for index, i := range w.Entries { + if entry.ID == i.ID { + w.Entries[index] = entry + return nil + } + } - return fmt.Errorf("unknown error during the update") + return fmt.Errorf("unknown error during the update") } // Import a wallet from a json string func (w *Wallet) Import(data []byte) error { - var entries []Entry + var entries []Entry - err := json.Unmarshal([]byte(data), &entries) - if err != nil { - return err - } + err := json.Unmarshal([]byte(data), &entries) + if err != nil { + return err + } - for _, entry := range entries { - err = w.AddEntry(entry) - if err != nil { - return err - } - } + for _, entry := range entries { + err = w.AddEntry(entry) + if err != nil { + return err + } + } - return nil + return nil } // Export a wallet to json func (w *Wallet) Export() ([]byte, error) { - data, err := json.Marshal(&w.Entries) - if err != nil { - return []byte{}, err - } + data, err := json.Marshal(&w.Entries) + if err != nil { + return []byte{}, err + } - return data, nil + return data, nil } diff --git a/gpm/wallet_test.go b/gpm/wallet_test.go index 3adb4a6..c66fb96 100644 --- a/gpm/wallet_test.go +++ b/gpm/wallet_test.go @@ -1,235 +1,235 @@ package gpm import ( - "fmt" - "os" - "io/ioutil" - "testing" + "fmt" + "io/ioutil" + "os" + "testing" ) func generateWalletWithEntries() Wallet { - var wallet Wallet + var wallet Wallet - for i := 0; i < 10; i++ { - entry := Entry{ ID: fmt.Sprintf("%d", i), Name: fmt.Sprintf("Entry %d", i), Group: "Good Group" } - wallet.AddEntry(entry) - } + for i := 0; i < 10; i++ { + entry := Entry{ID: fmt.Sprintf("%d", i), Name: fmt.Sprintf("Entry %d", i), Group: "Good Group"} + wallet.AddEntry(entry) + } - return wallet + return wallet } func TestAddBadEntry(t *testing.T) { - var entry Entry - var wallet Wallet + var entry Entry + var wallet Wallet - err := wallet.AddEntry(entry) - if err == nil { - t.Error("a bad entry must return an error") - } + err := wallet.AddEntry(entry) + if err == nil { + t.Error("a bad entry must return an error") + } } func TestAddEntries(t *testing.T) { - var wallet Wallet + var wallet Wallet - for i := 0; i < 10; i++ { - entry := Entry{ ID: fmt.Sprintf("%d", i), Name: fmt.Sprintf("Entry %d", i) } - err := wallet.AddEntry(entry) - if err != nil { - t.Errorf("a good entry mustn't return an error: %s", err) - } - } + for i := 0; i < 10; i++ { + entry := Entry{ID: fmt.Sprintf("%d", i), Name: fmt.Sprintf("Entry %d", i)} + err := wallet.AddEntry(entry) + if err != nil { + t.Errorf("a good entry mustn't return an error: %s", err) + } + } - if len(wallet.Entries) != 10 { - t.Errorf("must have 10 entries: %d", len(wallet.Entries)) - } + if len(wallet.Entries) != 10 { + t.Errorf("must have 10 entries: %d", len(wallet.Entries)) + } } func TestSearchEntryWithBadID(t *testing.T) { - wallet := generateWalletWithEntries() - entry := wallet.SearchEntryByID("BAD-ID") - if entry.ID != "" { - t.Errorf("if the entry doesn't exist must return an empty Entry: %s", entry.ID) - } + wallet := generateWalletWithEntries() + entry := wallet.SearchEntryByID("BAD-ID") + if entry.ID != "" { + t.Errorf("if the entry doesn't exist must return an empty Entry: %s", entry.ID) + } } func TestSearchEntryWithGoodID(t *testing.T) { - wallet := generateWalletWithEntries() - entry := wallet.SearchEntryByID("5") - if entry.ID != "5" { - t.Errorf("the ID entry must be 5: %s", entry.ID) - } + wallet := generateWalletWithEntries() + entry := wallet.SearchEntryByID("5") + if entry.ID != "5" { + t.Errorf("the ID entry must be 5: %s", entry.ID) + } } func TestSearchEntriesByGroup(t *testing.T) { - wallet := generateWalletWithEntries() - entries := len(wallet.SearchEntry("", "BAD-GROUP")) - if entries != 0 { - t.Errorf("a search with bad group must return 0 entry: %d", entries) - } + wallet := generateWalletWithEntries() + entries := len(wallet.SearchEntry("", "BAD-GROUP")) + if entries != 0 { + t.Errorf("a search with bad group must return 0 entry: %d", entries) + } - entries = len(wallet.SearchEntry("", "good group")) - if entries != 10 { - t.Errorf("a search with good group must return 10 entries: %d", entries) - } + entries = len(wallet.SearchEntry("", "good group")) + if entries != 10 { + t.Errorf("a search with good group must return 10 entries: %d", entries) + } } func TestSearchEntriesByPattern(t *testing.T) { - wallet := generateWalletWithEntries() - entries := len(wallet.SearchEntry("BAD-PATTERN", "")) - if entries != 0 { - t.Errorf("a search with bad pattern must return 0 entry: %d", entries) - } + wallet := generateWalletWithEntries() + entries := len(wallet.SearchEntry("BAD-PATTERN", "")) + if entries != 0 { + t.Errorf("a search with bad pattern must return 0 entry: %d", entries) + } - entries = len(wallet.SearchEntry("entry", "")) - if entries != 10 { - t.Errorf("a search with good pattern must return 10 entries: %d", entries) - } + entries = len(wallet.SearchEntry("entry", "")) + if entries != 10 { + t.Errorf("a search with good pattern must return 10 entries: %d", entries) + } - entries = len(wallet.SearchEntry("^entry 5$", "")) - if entries != 1 { - t.Errorf("a search with specific pattern must return 1 entry: %d", entries) - } + entries = len(wallet.SearchEntry("^entry 5$", "")) + if entries != 1 { + t.Errorf("a search with specific pattern must return 1 entry: %d", entries) + } } func TestSearchEntriesByPatternAndGroup(t *testing.T) { - wallet := generateWalletWithEntries() - entries := len(wallet.SearchEntry("entry", "good group")) - if entries != 10 { - t.Errorf("a search with good pattern and godd group must return 10 entries: %d", entries) - } + wallet := generateWalletWithEntries() + entries := len(wallet.SearchEntry("entry", "good group")) + if entries != 10 { + t.Errorf("a search with good pattern and godd group must return 10 entries: %d", entries) + } } func TestDeleteNotExistingEntry(t *testing.T) { - wallet := generateWalletWithEntries() - err := wallet.DeleteEntry("BAD-ID") - if err == nil { - t.Error("if the entry doesn't exist must return an error") - } + wallet := generateWalletWithEntries() + err := wallet.DeleteEntry("BAD-ID") + if err == nil { + t.Error("if the entry doesn't exist must return an error") + } - if len(wallet.Entries) != 10 { - t.Errorf("must have 10 entries: %d", len(wallet.Entries)) - } + if len(wallet.Entries) != 10 { + t.Errorf("must have 10 entries: %d", len(wallet.Entries)) + } } func TestDeleteEntry(t *testing.T) { - wallet := generateWalletWithEntries() - err := wallet.DeleteEntry("5") - if err != nil { - t.Errorf("a good entry mustn't return an error: %s", err) - } + wallet := generateWalletWithEntries() + err := wallet.DeleteEntry("5") + if err != nil { + t.Errorf("a good entry mustn't return an error: %s", err) + } - if len(wallet.Entries) != 9 { - t.Errorf("must have 9 entries: %d", len(wallet.Entries)) - } + if len(wallet.Entries) != 9 { + t.Errorf("must have 9 entries: %d", len(wallet.Entries)) + } - if wallet.SearchEntryByID("5").ID != "" { - t.Error("must return an empty entry for the ID 5") - } + if wallet.SearchEntryByID("5").ID != "" { + t.Error("must return an empty entry for the ID 5") + } } func TestUpdateNotExistingEntry(t *testing.T) { - wallet := generateWalletWithEntries() - err := wallet.UpdateEntry(Entry{ID: "BAD-ID"}) - if err == nil { - t.Error("if the entry doesn't exist must return an error") - } + wallet := generateWalletWithEntries() + err := wallet.UpdateEntry(Entry{ID: "BAD-ID"}) + if err == nil { + t.Error("if the entry doesn't exist must return an error") + } } func TestUpdateEntry(t *testing.T) { - wallet := generateWalletWithEntries() - err := wallet.UpdateEntry(Entry{ID: "5"}) - if err == nil { - t.Error("if the entry is bad must return an error") - } + wallet := generateWalletWithEntries() + err := wallet.UpdateEntry(Entry{ID: "5"}) + if err == nil { + t.Error("if the entry is bad must return an error") + } - err = wallet.UpdateEntry(Entry{ID: "5", Name: "Name 5"}) - if err != nil { - t.Errorf("a good entry mustn't return an error: %s", err) - } + err = wallet.UpdateEntry(Entry{ID: "5", Name: "Name 5"}) + if err != nil { + t.Errorf("a good entry mustn't return an error: %s", err) + } - entry := wallet.SearchEntryByID("5") - if entry.Name != "Name 5" { - t.Errorf("the entry name for the ID 5 must be 'Name 5': %s", entry.Name) - } + entry := wallet.SearchEntryByID("5") + if entry.Name != "Name 5" { + t.Errorf("the entry name for the ID 5 must be 'Name 5': %s", entry.Name) + } } func TestExportAndImport(t *testing.T) { - wallet := generateWalletWithEntries() - export, err := wallet.Export() - if err != nil { - t.Errorf("an export mustn't return an error: %s", err) - } + wallet := generateWalletWithEntries() + export, err := wallet.Export() + if err != nil { + t.Errorf("an export mustn't return an error: %s", err) + } - wallet = Wallet{} - err = wallet.Import(export) - if err != nil { - t.Errorf("a good import mustn't return an error: %s", err) - } + wallet = Wallet{} + err = wallet.Import(export) + if err != nil { + t.Errorf("a good import mustn't return an error: %s", err) + } - entries := len(wallet.Entries) - if entries != 10 { - t.Errorf("must have 10 entries: %d", entries) - } + entries := len(wallet.Entries) + if entries != 10 { + t.Errorf("must have 10 entries: %d", entries) + } } func TestSaveWallet(t *testing.T) { - tmpFile, _ := ioutil.TempFile(os.TempDir(), "gpm_test-") - defer os.Remove(tmpFile.Name()) + tmpFile, _ := ioutil.TempFile(os.TempDir(), "gpm_test-") + defer os.Remove(tmpFile.Name()) - wallet := generateWalletWithEntries() - wallet.Path = tmpFile.Name() - wallet.Passphrase = "secret" + wallet := generateWalletWithEntries() + wallet.Path = tmpFile.Name() + wallet.Passphrase = "secret" - err := wallet.Save() - if err != nil { - t.Errorf("save wallet mustn't return an error: %s", err) - } + err := wallet.Save() + if err != nil { + t.Errorf("save wallet mustn't return an error: %s", err) + } } func TestLoadWalletWithGoodPassword(t *testing.T) { - var loadWallet Wallet + var loadWallet Wallet - tmpFile, _ := ioutil.TempFile(os.TempDir(), "gpm_test-") - defer os.Remove(tmpFile.Name()) + tmpFile, _ := ioutil.TempFile(os.TempDir(), "gpm_test-") + defer os.Remove(tmpFile.Name()) - wallet := generateWalletWithEntries() - wallet.Path = tmpFile.Name() - wallet.Passphrase = "secret" - wallet.Save() - loadWallet.Path = wallet.Path - loadWallet.Passphrase = wallet.Passphrase + wallet := generateWalletWithEntries() + wallet.Path = tmpFile.Name() + wallet.Passphrase = "secret" + wallet.Save() + loadWallet.Path = wallet.Path + loadWallet.Passphrase = wallet.Passphrase - err := loadWallet.Load() - if err != nil { - t.Errorf("load wallet mustn't return an error: %s", err) - } + err := loadWallet.Load() + if err != nil { + t.Errorf("load wallet mustn't return an error: %s", err) + } - entries := len(loadWallet.Entries) - if entries != 10 { - t.Errorf("must have 10 entries: %d", entries) - } + entries := len(loadWallet.Entries) + if entries != 10 { + t.Errorf("must have 10 entries: %d", entries) + } } func TestLoadWalletWithBadPassword(t *testing.T) { - var loadWallet Wallet + var loadWallet Wallet - tmpFile, _ := ioutil.TempFile(os.TempDir(), "gpm_test-") - defer os.Remove(tmpFile.Name()) + tmpFile, _ := ioutil.TempFile(os.TempDir(), "gpm_test-") + defer os.Remove(tmpFile.Name()) - wallet := generateWalletWithEntries() - wallet.Path = tmpFile.Name() - wallet.Passphrase = "secret" - wallet.Save() - loadWallet.Path = wallet.Path - loadWallet.Passphrase = "bad secret" + wallet := generateWalletWithEntries() + wallet.Path = tmpFile.Name() + wallet.Passphrase = "secret" + wallet.Save() + loadWallet.Path = wallet.Path + loadWallet.Passphrase = "bad secret" - err := loadWallet.Load() - if err == nil { - t.Error("load wallet with bad password must return an error") - } + err := loadWallet.Load() + if err == nil { + t.Error("load wallet with bad password must return an error") + } - entries := len(loadWallet.Entries) - if entries != 0 { - t.Errorf("must have 0 entries: %d", entries) - } + entries := len(loadWallet.Entries) + if entries != 0 { + t.Errorf("must have 0 entries: %d", entries) + } }