passgo/main.go

223 lines
4.7 KiB
Go
Raw Normal View History

2019-09-05 06:41:39 -04:00
package passgo
2019-09-04 22:19:39 -04:00
import (
"bufio"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"os/user"
"path"
"regexp"
"sort"
"strings"
"github.com/jcmdev0/gpgagent"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)
var (
basename *regexp.Regexp
2019-09-05 06:41:39 -04:00
ask openpgp.PromptFunction
2019-09-04 22:19:39 -04:00
)
func init() {
basename = regexp.MustCompile(".gpg$")
}
type Store struct {
2019-09-05 06:41:39 -04:00
Dir string
2019-09-04 22:19:39 -04:00
keyring openpgp.KeyRing
}
func GetStore(store *Store) error {
2019-09-05 06:41:39 -04:00
u, err := user.Current()
if err != nil {
return fmt.Errorf("Can't get current user.")
}
if store.Dir == "" {
store.Dir = path.Join(u.HomeDir, ".password-store")
2019-09-05 06:41:39 -04:00
}
2019-09-04 22:19:39 -04:00
2019-09-05 06:41:39 -04:00
fd, err := os.Open(path.Join(u.HomeDir, ".gnupg/secring.gpg"))
2019-09-04 22:19:39 -04:00
defer fd.Close()
if err != nil {
return fmt.Errorf("Can't open keyring file")
2019-09-04 22:19:39 -04:00
}
kr, err := openpgp.ReadKeyRing(fd)
if err != nil {
return fmt.Errorf("Can't open gnupg keyring.")
2019-09-04 22:19:39 -04:00
}
store.keyring = kr
return nil
2019-09-04 22:19:39 -04:00
}
type caseInsensitive []os.FileInfo
2019-09-05 06:41:39 -04:00
func (fs caseInsensitive) Len() int { return len(fs) }
2019-09-04 22:19:39 -04:00
func (fs caseInsensitive) Swap(i, j int) { fs[i], fs[j] = fs[j], fs[i] }
func (fs caseInsensitive) Less(i, j int) bool {
2019-09-05 06:41:39 -04:00
return strings.ToLower(fs[i].Name()) < strings.ToLower(fs[j].Name())
2019-09-04 22:19:39 -04:00
}
type Pass struct {
Pathname string
2019-09-05 06:41:39 -04:00
Level int
Dir bool
2019-09-04 22:19:39 -04:00
}
2019-09-05 06:41:39 -04:00
func (s *Store) List() ([]Pass, error) {
return s.list(0, "")
2019-09-04 22:19:39 -04:00
}
2019-09-05 06:41:39 -04:00
func (s *Store) list(level int, p string) ([]Pass, error) {
ret := make([]Pass, 0)
dir := path.Join(s.Dir, p)
2019-09-04 22:19:39 -04:00
fd, err := os.Open(dir)
defer fd.Close()
if err != nil {
return nil, fmt.Errorf("Cannot open password store")
}
files, err := fd.Readdir(0)
if err != nil {
return nil, fmt.Errorf("Cannot read password store")
}
sort.Sort(caseInsensitive(files))
for _, x := range files {
2019-09-05 06:41:39 -04:00
n := basename.ReplaceAllLiteralString(x.Name(), "")
entry := Pass{Pathname: path.Join(p, n), Level: level}
2019-09-04 22:19:39 -04:00
if n[0] == '.' {
continue
}
if x.IsDir() {
entry.Dir = true
2019-09-05 06:41:39 -04:00
ret = append(ret, entry)
l, err := s.list(level+1, path.Join(p, x.Name()))
2019-09-04 22:19:39 -04:00
if err != nil {
2019-09-05 06:41:39 -04:00
return nil, err
2019-09-04 22:19:39 -04:00
}
2019-09-05 06:41:39 -04:00
ret = append(ret, l...)
2019-09-04 22:19:39 -04:00
} else {
2019-09-05 06:41:39 -04:00
ret = append(ret, entry)
2019-09-04 22:19:39 -04:00
}
}
2019-09-05 06:41:39 -04:00
return ret, nil
2019-09-04 22:19:39 -04:00
}
func AskPass(prompts ...func() []byte) openpgp.PromptFunction {
var prompt func() []byte
if len(prompts) > 0 {
prompt = prompts[0]
} else {
prompt = func() []byte {
fmt.Print("AskPass(): enter passphrase: ")
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
return []byte(text[:len(text)-1])
}
}
var passphrase []byte
2019-09-06 11:43:14 -04:00
dec := func(p *packet.PrivateKey) error {
2019-09-05 06:41:39 -04:00
for i := 0; i < 3; i++ {
2019-09-06 11:43:14 -04:00
if err := p.Decrypt(passphrase); err == nil {
return err
2019-09-04 22:19:39 -04:00
}
passphrase = prompt()
2019-09-06 11:43:14 -04:00
if passphrase == nil {
return fmt.Errorf("Passphrase entry aborted")
}
2019-09-04 22:19:39 -04:00
}
2019-09-06 11:43:14 -04:00
return fmt.Errorf("Passphrase entry failed")
2019-09-04 22:19:39 -04:00
}
var ret openpgp.PromptFunction
ret = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if !symmetric {
2019-09-05 06:41:39 -04:00
for _, k := range keys {
2019-09-04 22:19:39 -04:00
if p := k.PrivateKey; p != nil && p.Encrypted {
2019-09-06 11:43:14 -04:00
if err := dec(p); err != nil {
return nil, err
}
2019-09-04 22:19:39 -04:00
}
for _, s := range k.Entity.Subkeys {
if p := s.PrivateKey; p != nil && p.Encrypted {
2019-09-06 11:43:14 -04:00
if err := dec(p); err != nil {
return nil, err
}
2019-09-04 22:19:39 -04:00
}
}
}
}
2019-09-06 11:43:14 -04:00
return passphrase, nil
2019-09-04 22:19:39 -04:00
}
return ret
}
//https://github.com/jcmdev0/gpgagent/blob/master/example/example.go
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}
passphrase, err := conn.GetPassphrase(&request)
if err != nil {
return nil, err
}
err = key.PrivateKey.Decrypt([]byte(passphrase))
if err != nil {
return nil, err
}
return []byte(passphrase), nil
}
return nil, fmt.Errorf("Unable to find key")
}
2019-09-05 06:41:39 -04:00
func (s *Store) Decrypt(name string, prompts ...func() []byte) (string, error) {
if ask == nil {
2019-09-04 22:19:39 -04:00
ask = AskPass(prompts...)
}
2019-09-05 06:41:39 -04:00
file := path.Join(s.Dir, strings.Join([]string{name, ".gpg"}, ""))
2019-09-04 22:19:39 -04:00
if _, err := os.Stat(file); os.IsNotExist(err) {
2019-09-05 06:41:39 -04:00
return "", fmt.Errorf("Not in password store.")
2019-09-04 22:19:39 -04:00
}
fd, err := os.Open(file)
defer fd.Close()
if err != nil {
2019-09-05 06:41:39 -04:00
return "", err
2019-09-04 22:19:39 -04:00
}
var reader io.Reader
unarmor, err := armor.Decode(fd)
if err == nil {
reader = unarmor.Body
} else {
fd.Close()
fd, err = os.Open(file)
defer fd.Close()
if err != nil {
2019-09-05 06:41:39 -04:00
return "", err
2019-09-04 22:19:39 -04:00
}
reader = fd
}
2019-09-05 06:41:39 -04:00
md, err := openpgp.ReadMessage(reader, s.keyring, ask, nil)
2019-09-04 22:19:39 -04:00
if err != nil {
fmt.Println("Error reading message")
2019-09-05 06:41:39 -04:00
return "", err
2019-09-04 22:19:39 -04:00
}
ra, err := ioutil.ReadAll(md.UnverifiedBody)
if err != nil {
2019-09-05 06:41:39 -04:00
return "", err
2019-09-04 22:19:39 -04:00
}
return string(ra), nil
}