style: change space to tab for goreport

This commit is contained in:
Adrien Waksberg 2019-08-31 19:18:35 +02:00
parent 30ada1a1fc
commit 3f9d4850b6
13 changed files with 853 additions and 842 deletions

View file

@ -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

View file

@ -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

View file

@ -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()
}

View file

@ -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")
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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()
}
}

View file

@ -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
}

View file

@ -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)
}
}