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