2019-09-06 11:43:14 -04:00
|
|
|
//+build darwin
|
2019-09-04 22:19:39 -04:00
|
|
|
|
2019-09-05 06:41:39 -04:00
|
|
|
package passgo
|
2019-09-04 22:19:39 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2019-09-10 11:31:57 -04:00
|
|
|
"encoding/hex"
|
2019-09-05 06:41:39 -04:00
|
|
|
"fmt"
|
2019-09-04 22:19:39 -04:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"os/user"
|
|
|
|
"path"
|
2019-09-10 08:45:59 -04:00
|
|
|
"strings"
|
2019-09-04 22:19:39 -04:00
|
|
|
"strconv"
|
2019-09-10 08:45:59 -04:00
|
|
|
"sync"
|
|
|
|
|
2019-09-10 11:31:57 -04:00
|
|
|
"golang.org/x/crypto/openpgp"
|
|
|
|
|
|
|
|
"github.com/jcmdev0/gpgagent"
|
2019-09-10 08:45:59 -04:00
|
|
|
"github.com/fsnotify/fsnotify"
|
|
|
|
)
|
|
|
|
|
2019-09-10 11:31:57 -04:00
|
|
|
func (s *Store) nativeDecrypt(name string) (string, error) {
|
2019-09-10 08:45:59 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-09-10 11:31:57 -04:00
|
|
|
func (s *Store) nativeEncrypt(pw string) ([]byte, error) {
|
2019-09-10 08:45:59 -04:00
|
|
|
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
|
2019-09-04 22:19:39 -04:00
|
|
|
)
|
|
|
|
|
2019-09-10 11:31:57 -04:00
|
|
|
func nativeIdentities() ([]string, error) {
|
2019-09-10 08:45:59 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-09-04 22:19:39 -04:00
|
|
|
func setAgentInfo() {
|
2019-09-04 22:43:29 -04:00
|
|
|
// get UID of current user
|
2019-09-04 22:19:39 -04:00
|
|
|
usr, err := user.Current()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error: cannot get user ID: %s\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
uid := usr.Uid
|
|
|
|
|
|
|
|
// get GPG Agent PID
|
2019-09-04 22:43:29 -04:00
|
|
|
// look for gpg-agent running as the current user:
|
2019-09-04 22:19:39 -04:00
|
|
|
output, err := exec.Command("pgrep", "-U", uid, "gpg-agent").Output()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error: %s\n", err)
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
fmt.Printf("gpg-agent process number is %s\n", output)
|
|
|
|
}
|
2019-09-04 22:43:29 -04:00
|
|
|
// trim trailing /n and convert to int
|
2019-09-04 22:19:39 -04:00
|
|
|
pid, err := strconv.Atoi(string(output[:len(output)-1]))
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Integer conversion failed: %s\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// find agent socket file
|
2019-09-04 22:43:29 -04:00
|
|
|
// find unix domain sockets opened by the current user's gpg-agent
|
2019-09-04 22:19:39 -04:00
|
|
|
cmd := exec.Command("lsof", "-w", "-Fn", "-u", uid, "-baUcgpg-agent")
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error: connect stdout to lsof: %s\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
scanner := bufio.NewScanner(stdout)
|
|
|
|
err = cmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error: cannot run lsof: %s\n", err)
|
|
|
|
}
|
|
|
|
|
2019-09-04 22:43:29 -04:00
|
|
|
// look for a socket named "S.gpg-agent"
|
2019-09-04 22:19:39 -04:00
|
|
|
filename := ""
|
|
|
|
for scanner.Scan() {
|
|
|
|
x := scanner.Text()
|
|
|
|
if x[0] != 'n' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
x = x[1:]
|
|
|
|
if path.Base(x) == "S.gpg-agent" {
|
|
|
|
fmt.Println(x)
|
|
|
|
filename = x
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cmd.Wait()
|
|
|
|
|
|
|
|
if filename == "" {
|
|
|
|
fmt.Printf("Error: gpg-agent socket file not found\n")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
s := fmt.Sprintf("%s:%d:1", filename, pid)
|
|
|
|
fmt.Printf("GPG_AGENT_INFO = %s\n", s)
|
2019-09-05 06:41:39 -04:00
|
|
|
os.Setenv("GPG_AGENT_INFO", s)
|
2019-09-04 22:43:29 -04:00
|
|
|
|
|
|
|
// gpg-agent is running, so use GPGPrompt as password prompt
|
2019-09-04 22:21:01 -04:00
|
|
|
ask = GPGPrompt
|
2019-09-10 08:45:59 -04:00
|
|
|
|
2019-09-04 22:19:39 -04:00
|
|
|
}
|
|
|
|
|
2019-09-10 11:31:57 -04:00
|
|
|
func GPGPrompt(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
|
|
|
conn, err := gpgagent.NewGpgAgentConn()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
for _, key := range keys {
|
|
|
|
cacheId := strings.ToUpper(hex.EncodeToString(key.PublicKey.Fingerprint[:]))
|
|
|
|
request := gpgagent.PassphraseRequest{CacheKey: cacheId}
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
var passphrase string
|
|
|
|
passphrase, err = conn.GetPassphrase(&request)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
err = key.PrivateKey.Decrypt([]byte(passphrase))
|
|
|
|
if err != nil {
|
|
|
|
conn.RemoveFromCache(cacheId)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return []byte(passphrase), nil
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("Unable to find key")
|
|
|
|
}
|
|
|
|
|
2019-09-04 22:19:39 -04:00
|
|
|
func init() {
|
|
|
|
setAgentInfo()
|
2019-09-10 08:45:59 -04:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2019-09-04 22:19:39 -04:00
|
|
|
}
|
|
|
|
|
2019-09-04 22:43:29 -04:00
|
|
|
//Clip copies a string to the clipboard
|
2019-09-04 22:19:39 -04:00
|
|
|
func Clip(x string) {
|
|
|
|
b := bytes.NewBuffer([]byte(x))
|
|
|
|
cmd := exec.Command("pbcopy")
|
|
|
|
cmd.Stdin = b
|
|
|
|
cmd.Run()
|
|
|
|
}
|
2019-09-10 08:45:59 -04:00
|
|
|
|