Compatibility with gpg 2.0 -- call out to gpg commands (on Darwin)

if go openpgp is not compatibile with the local gpg installation.
This commit is contained in:
Greg 2019-09-10 08:45:59 -04:00
parent 44cab96fff
commit 09859ca0a9
4 changed files with 372 additions and 23 deletions

View File

@ -49,7 +49,7 @@ func main() {
log(Info, " StoreDir = ", store.Dir)
err = passgo.GetStore(&store)
if err != nil {
log(Fatal, err)
log(Info, err)
}
if Config.ClearDelay == 0 {
Config.ClearDelay = 45
@ -62,8 +62,10 @@ func main() {
reload = make(chan struct{})
updated = make(chan struct{})
chdir = make(chan struct{})
passch = make(chan []byte)
passgo.Identities()
go Updater()
log(Info, "Staring event loop")
go eventLoop()
@ -78,6 +80,7 @@ var (
store passgo.Store
reload chan struct{}
updated chan struct{}
chdir chan struct{}
passch chan []byte
)
@ -98,7 +101,8 @@ func Updater() {
if err != nil {
log(Fatal, err)
}
watcher.Add(store.Dir)
dir := store.Dir
watcher.Add(dir)
for {
select {
case <-reload:
@ -107,6 +111,13 @@ func Updater() {
update()
case e := <-watcher.Errors:
log(Info, "Watcher error: ", e)
case <-chdir:
err := watcher.Remove(dir)
if err != nil {
log(Info, "Error removing watcher: ", err)
}
watcher.Add(store.Dir)
update()
}
}
}
@ -151,10 +162,10 @@ func eventLoop() {
margin := layout.UniformInset(ui.Dp(10))
title := &text.Label{Face: face, Text: "passgo"}
dotsbtn := &Button{
dotsBtn := &Button{
Face: face,
Label: "\xe2\x8b\xae",
Alignment: text.End,
Alignment: text.Middle,
Color: black,
Background: gray,
}
@ -200,6 +211,30 @@ func eventLoop() {
}
updateBtns()
idBtns := make([]*Button, 0)
updateIdBtns := func() {
ids, err := passgo.Identities()
if err != nil {
log(Info, err)
return
}
for i,n := range ids {
if i >= len(idBtns) {
idBtns = append(idBtns, &Button{
Face: face,
Label: n,
Alignment: text.End,
Color: black,
Background: gray,
})
} else {
idBtns[i].Label = n
}
}
idBtns = idBtns[:len(ids)]
}
confBtn := &Button{
Face: face,
Label: "configure",
@ -225,6 +260,14 @@ func eventLoop() {
Color: black,
Background: gray,
}
confirmLabel := &text.Label{Face: face, Text: "Password exists. Overwrite?"}
yesBtn := &Button{
Face: face,
Label: "yes",
Alignment: text.End,
Color: black,
Background: gray,
}
promptLabel := &text.Label{Face: face, Text: "passphrase"}
promptEd := &text.Editor{Face: face, SingleLine: true, Submit: true}
@ -235,6 +278,22 @@ func eventLoop() {
Color: black,
Background: gray,
}
plusBtn := &Button{
Face: face,
Label: "+",
Alignment: text.Middle,
Color: black,
Background: gray,
}
insertLabel := &text.Label{Face: face, Text: "Insert"}
passnameLabel := &text.Label{Face: face, Text: "password name:"}
passnameEd := &text.Editor{Face: face, SingleLine: true, Submit: true}
passvalLabel := &text.Label{Face: face, Text: "password value:"}
passvalEd := &text.Editor{Face: face, SingleLine: true, Submit: true}
noidLabel := &text.Label{Face: face, Text: "No GPG ids available. Please create a private key"}
idLabel := &text.Label{Face: face, Text: "Select ID"}
anim := &time.Ticker{}
animating := false
@ -250,7 +309,7 @@ func eventLoop() {
animating = false
}
var listPage, confPage, promptPage, page func()
var listPage, idPage, insertPage, confirmPage, confPage, promptPage, page func()
prompt := func() []byte {
page = promptPage
@ -271,14 +330,19 @@ func eventLoop() {
key.HideInputOp{}.Add(ops)
}
var c2 layout.FlexChild
if len(passBtns) == 0 {
switch {
case store.Empty:
c2 = flex.End(confBtn.Layout(c, q, ops, cs))
if confBtn.Clicked() {
log(Info, "Configure")
w.Invalidate()
page = confPage
}
} else {
case store.Id == "":
c2 = flex.End(idLabel.Layout(ops, cs))
w.Invalidate()
page = idPage
default:
for lst.Init(c, q, ops, cs, len(passBtns)); lst.More(); lst.Next() {
i := lst.Index()
btn := passBtns[i]
@ -316,11 +380,6 @@ func eventLoop() {
c2 = flex.End(lst.Layout())
}
mux.Unlock()
if dotsbtn.Clicked() {
log(Info, "Configure")
w.Invalidate()
page = confPage
}
flex.Layout(c1, c2)
x := time.Since(overlayStart).Seconds()
if x >= fade1b && x < start2 && animating {
@ -372,6 +431,99 @@ func eventLoop() {
}
}
idPage = func() {
if !animating {
animOn()
}
cs = flex.Rigid()
c2 := flex.End(idLabel.Layout(ops, cs))
var c3 layout.FlexChild
//if len(idBtns) == 0 {
updateIdBtns()
//}
cs = flex.Rigid()
if len(idBtns) == 0 { // still zero after update
c3 = flex.End(noidLabel.Layout(ops, cs))
} else {
for lst.Init(c, q, ops, cs, len(idBtns)); lst.More(); lst.Next() {
lst.End(idBtns[lst.Index()].Layout(c, q, ops, lst.Constraints()))
}
c3 = flex.End(lst.Layout())
for _, btn := range idBtns {
if btn.Clicked() {
log(Info, "ID selected: ", btn.Label)
store.SetID(btn.Label)
w.Invalidate()
animOff()
page = listPage
}
}
}
flex.Layout(c1, c2, c3)
}
var insName, insValue string
insertPage = func() {
cs = flex.Rigid()
c2 := flex.End(insertLabel.Layout(ops, cs))
c3 := flex.End(passnameLabel.Layout(ops, flex.Rigid()))
c4 := flex.End(passnameEd.Layout(c, q, ops, flex.Rigid()))
c5 := flex.End(passvalLabel.Layout(ops, flex.Rigid()))
c6 := flex.End(passvalEd.Layout(c, q, ops, flex.Rigid()))
cs = flex.Rigid()
al := &layout.Align{Alignment: layout.E}
btnflx := &layout.Flex{Axis: layout.Horizontal}
btnflx.Init(ops, al.Begin(ops, cs))
bc1 := btnflx.End(backBtn.Layout(c, q, ops, btnflx.Rigid()))
bc2 := btnflx.End(saveBtn.Layout(c, q, ops, btnflx.Rigid()))
c7 := flex.End(al.End(btnflx.Layout(bc1, bc2)))
flex.Layout(c1, c2, c3, c4, c5, c6, c7)
if backBtn.Clicked() {
w.Invalidate()
page = listPage
}
if saveBtn.Clicked() {
w.Invalidate()
page = listPage
insName = passnameEd.Text()
insValue = passvalEd.Text()
for _, n := range pathnames {
if insName == n {
log(Info,"Password exists")
page = confirmPage
w.Invalidate()
return
}
}
store.Insert(passnameEd.Text(), passvalEd.Text())
}
}
confirmPage = func() {
cs = flex.Rigid()
c2 := flex.End(confirmLabel.Layout(ops, cs))
al := &layout.Align{Alignment: layout.E}
btnflx := &layout.Flex{Axis: layout.Horizontal}
btnflx.Init(ops, al.Begin(ops, flex.Rigid()))
bc1 := btnflx.End(backBtn.Layout(c, q, ops, btnflx.Rigid()))
bc2 := btnflx.End(yesBtn.Layout(c, q, ops, btnflx.Rigid()))
c3 := flex.End(al.End(btnflx.Layout(bc1, bc2)))
flex.Layout(c1, c2, c3)
if backBtn.Clicked() {
w.Invalidate()
page = insertPage
}
if yesBtn.Clicked() {
w.Invalidate()
page = listPage
store.Insert(insName, insValue)
}
}
confPage = func() {
cs = flex.Rigid()
c2 := flex.End(storeDirLabel.Layout(ops, cs))
@ -399,10 +551,12 @@ func eventLoop() {
log(Info, "Save")
go func() { // do not block UI thread
store.Dir = storeDirEd.Text()
store.Id = ""
passgo.GetStore(&store)
Config.StoreDir = store.Dir
store.Mkdir()
saveConf()
reload <- struct{}{}
chdir <- struct{}{}
}()
w.Invalidate()
page = listPage
@ -477,15 +631,29 @@ func eventLoop() {
cs = flex.Rigid()
titleflex.Init(ops, cs)
cs = titleflex.Rigid()
ct2 := titleflex.End(dotsbtn.Layout(c, q, ops, cs))
ct2 := titleflex.End(plusBtn.Layout(c, q, ops, cs))
cs = titleflex.Rigid()
ct3 := titleflex.End(dotsBtn.Layout(c, q, ops, cs))
cs = titleflex.Flexible(1.0)
cs.Width.Min = cs.Width.Max
ct1 := titleflex.End(title.Layout(ops, cs))
c1 = flex.End(titleflex.Layout(ct1, ct2))
c1 = flex.End(titleflex.Layout(ct1, ct2, ct3))
page()
margin.End(dims)
if dotsBtn.Clicked() {
log(Info, "Configure")
w.Invalidate()
page = confPage
}
if plusBtn.Clicked() {
log(Info, "Plus")
w.Invalidate()
insName, insValue = "", ""
page = insertPage
}
w.Update(ops)
}
}

View File

@ -113,7 +113,7 @@ func (b *Button) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Const
Alignment: b.Alignment,
}
ins := layout.UniformInset(ui.Dp(4))
paint.ColorOp{Color: b.Color}.Add(ops)
//paint.ColorOp{Color: b.Color}.Add(ops)
dims = ins.End(l.Layout(ops, ins.Begin(c, ops, cs)))
pointer.RectAreaOp{image.Rect(0, 0, dims.Size.X, dims.Size.Y)}.Add(ops)
b.Click.Add(ops)

View File

@ -10,9 +10,75 @@ import (
"os/exec"
"os/user"
"path"
"strings"
"strconv"
"sync"
"github.com/fsnotify/fsnotify"
)
func (s *Store) gpgDecrypt(name string) (string, error) {
fmt.Println("calling gpg -d")
file := path.Join(s.Dir, strings.Join([]string{name, ".gpg"}, ""))
if _, err := os.Stat(file); os.IsNotExist(err) {
return "", fmt.Errorf("Not in password store.")
}
output, err := exec.Command("gpg", "-d", file).Output()
if err != nil {
return "", fmt.Errorf("Error running gpg: %s", err)
}
return string(output[:len(output)-1]), nil
}
func (s *Store) gpgEncrypt(pw string) ([]byte, error) {
if s.Id == "" {
return nil, fmt.Errorf("No ID")
}
fmt.Printf("Calling gpg -e -r %s\n", s.Id)
cmd := exec.Command("gpg", "-e", "-r", s.Id)
cmd.Stdin = strings.NewReader(pw + "\n")
output, err := cmd.Output()
if err != nil {
fmt.Printf("Error running GPG: %s\n", err)
return nil, err
}
fmt.Println("success")
return output, nil
}
var (
idMux sync.Mutex
cachedIdentities []string
)
func gpgIdentities() ([]string, error) {
idMux.Lock()
defer idMux.Unlock()
if cachedIdentities != nil {
ret := make([]string,len(cachedIdentities))
copy(ret, cachedIdentities)
return ret, nil
}
ret := make([]string,0)
fmt.Println("calling gpg")
output, err := exec.Command("gpg", "--list-secret-keys", "--with-colons").Output()
if err != nil {
return nil, fmt.Errorf("Error running gpg: %s", err)
}
scanner := bufio.NewScanner(bytes.NewBuffer(output))
for scanner.Scan() {
fs := strings.Split(scanner.Text(),":")
if fs[0] == "uid" {
fmt.Printf("%s: %s\n",fs[0], fs[9])
ret = append(ret, fs[9])
}
}
if len(ret) > 0 {
cachedIdentities = ret
}
return ret, nil
}
func setAgentInfo() {
// get UID of current user
usr, err := user.Current()
@ -77,10 +143,33 @@ func setAgentInfo() {
// gpg-agent is running, so use GPGPrompt as password prompt
ask = GPGPrompt
}
func init() {
setAgentInfo()
go func() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
fmt.Printf("Cannot create filesystem watcher: %s\n", err)
return
}
u, err := user.Current()
if err != nil {
fmt.Printf("Cannot get current user: %s\n", err)
return
}
watcher.Add(path.Join(u.HomeDir, ".gnupg"))
fmt.Println("Watching ", path.Join(u.HomeDir, ".gnupg"))
for {
select {
case <-watcher.Events:
idMux.Lock()
cachedIdentities = nil
idMux.Unlock()
}
}
}()
}
//Clip copies a string to the clipboard
@ -90,3 +179,4 @@ func Clip(x string) {
cmd.Stdin = b
cmd.Run()
}

105
main.go
View File

@ -12,6 +12,7 @@ import (
"regexp"
"sort"
"strings"
"time"
"github.com/jcmdev0/gpgagent"
@ -23,15 +24,21 @@ import (
var (
basename *regexp.Regexp
ask openpgp.PromptFunction
Keyring openpgp.KeyRing
krTime time.Time
useGPG bool
homeDir string
)
func init() {
useGPG = true // default unless we can get the Go openpgp code to work
basename = regexp.MustCompile(".gpg$")
}
type Store struct {
Dir string
keyring openpgp.KeyRing
Id string
Empty bool
}
func GetStore(store *Store) error {
@ -42,20 +49,70 @@ func GetStore(store *Store) error {
if store.Dir == "" {
store.Dir = path.Join(u.HomeDir, ".password-store")
}
if _, err := os.Stat(store.Dir); os.IsNotExist(err) {
store.Empty = true
}
id, err := ioutil.ReadFile(path.Join(store.Dir, ".gpg-id"))
if err != nil {
return fmt.Errorf("Can't read .gpg-id: %s", err)
}
if id[len(id)-1] == '\n' {
id = id[:len(id)-1]
}
store.Id = string(id)
return getKeyring()
}
fd, err := os.Open(path.Join(u.HomeDir, ".gnupg/secring.gpg"))
func getKeyring() error {
u, err := user.Current()
homeDir = u.HomeDir
krfile := path.Join(homeDir, ".gnupg/secring.gpg")
if fi, err := os.Stat(krfile); os.IsNotExist(err) {
return nil
} else {
if krTime.Sub(fi.ModTime()) > 0 { // already loaded
return nil
}
}
fmt.Println("Loading secring.gpg")
fd, err := os.Open(krfile)
defer fd.Close()
if err != nil {
return fmt.Errorf("Can't open keyring file")
return nil
}
kr, err := openpgp.ReadKeyRing(fd)
if err != nil {
return fmt.Errorf("Can't open gnupg keyring.")
//return fmt.Errorf("Can't open gnupg keyring.")
return nil
}
store.keyring = kr
useGPG = false
Keyring = kr
krTime = time.Now()
return nil
}
func (s *Store) Mkdir() error {
err := os.MkdirAll(s.Dir, 0700)
if err != nil {
fmt.Printf("Cannot create store directory.")
return err
}
s.Empty = false
return nil
}
func (s *Store) SetID(id string) error {
s.Id = id
if _, err := os.Stat(s.Dir); os.IsNotExist(err) {
fmt.Printf("store directory does not exist.\n")
err := s.Mkdir()
if err != nil {
return err
}
}
return ioutil.WriteFile(path.Join(s.Dir, ".gpg-id"), []byte(id + "\n"), 0644)
}
type caseInsensitive []os.FileInfo
func (fs caseInsensitive) Len() int { return len(fs) }
@ -192,6 +249,9 @@ func GPGPrompt(keys []openpgp.Key, symmetric bool) ([]byte, error) {
}
func (s *Store) Decrypt(name string, prompts ...func() []byte) (string, error) {
if useGPG {
return s.gpgDecrypt(name)
}
if ask == nil {
ask = AskPass(prompts...)
}
@ -217,7 +277,7 @@ func (s *Store) Decrypt(name string, prompts ...func() []byte) (string, error) {
}
reader = fd
}
md, err := openpgp.ReadMessage(reader, s.keyring, ask, nil)
md, err := openpgp.ReadMessage(reader, Keyring, ask, nil)
if err != nil {
fmt.Println("Error reading message")
return "", err
@ -226,5 +286,36 @@ func (s *Store) Decrypt(name string, prompts ...func() []byte) (string, error) {
if err != nil {
return "", err
}
return string(ra), nil
if len(ra) > 1 {
return string(ra[:len(ra)-1]), nil
} else {
return "", fmt.Errorf("Password is empty")
}
}
func Identities() ([]string, error) {
getKeyring()
if useGPG {
return gpgIdentities()
}
ret := make([]string, 0)
for _, k := range Keyring.DecryptionKeys() {
for n, _ := range k.Entity.Identities {
ret = append(ret, n)
}
}
return ret, nil
}
func (s *Store) Insert(name, value string) error {
var enc []byte
var err error
//if !useGo { // golang openpgp code not implemented yet
fmt.Println("Calling gpgEncrypt")
enc, err = s.gpgEncrypt(value)
if err != nil {
return err
}
//}
return ioutil.WriteFile(path.Join(s.Dir, name + ".gpg"), enc, 0644)
}