package passgo 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 ask openpgp.PromptFunction ) func init() { basename = regexp.MustCompile(".gpg$") } type Store struct { Dir string keyring openpgp.KeyRing } func GetStore(store *Store) error { 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") } fd, err := os.Open(path.Join(u.HomeDir, ".gnupg/secring.gpg")) defer fd.Close() if err != nil { return fmt.Errorf("Can't open keyring file") } kr, err := openpgp.ReadKeyRing(fd) if err != nil { return fmt.Errorf("Can't open gnupg keyring.") } store.keyring = kr return nil } type caseInsensitive []os.FileInfo func (fs caseInsensitive) Len() int { return len(fs) } func (fs caseInsensitive) Swap(i, j int) { fs[i], fs[j] = fs[j], fs[i] } func (fs caseInsensitive) Less(i, j int) bool { return strings.ToLower(fs[i].Name()) < strings.ToLower(fs[j].Name()) } type Pass struct { Pathname string Level int Dir bool } func (s *Store) List() ([]Pass, error) { return s.list(0, "") } func (s *Store) list(level int, p string) ([]Pass, error) { ret := make([]Pass, 0) dir := path.Join(s.Dir, p) 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 { n := basename.ReplaceAllLiteralString(x.Name(), "") entry := Pass{Pathname: path.Join(p, n), Level: level} if n[0] == '.' { continue } if x.IsDir() { entry.Dir = true ret = append(ret, entry) l, err := s.list(level+1, path.Join(p, x.Name())) if err != nil { return nil, err } ret = append(ret, l...) } else { ret = append(ret, entry) } } return ret, nil } 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 dec := func(p *packet.PrivateKey) error { for i := 0; i < 3; i++ { if err := p.Decrypt(passphrase); err == nil { passphrase = nil return err } passphrase = prompt() if passphrase == nil { passphrase = nil return fmt.Errorf("Passphrase entry aborted") } } passphrase = nil return fmt.Errorf("Passphrase entry failed") } var ret openpgp.PromptFunction ret = func(keys []openpgp.Key, symmetric bool) ([]byte, error) { if !symmetric { for _, k := range keys { if p := k.PrivateKey; p != nil && p.Encrypted { if err := dec(p); err != nil { return nil, err } } for _, s := range k.Entity.Subkeys { if p := s.PrivateKey; p != nil && p.Encrypted { if err := dec(p); err != nil { return nil, err } } } } } return passphrase, nil } 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} 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") } func (s *Store) Decrypt(name string, prompts ...func() []byte) (string, error) { if ask == nil { ask = AskPass(prompts...) } 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.") } fd, err := os.Open(file) defer fd.Close() if err != nil { return "", err } 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 { return "", err } reader = fd } md, err := openpgp.ReadMessage(reader, s.keyring, ask, nil) if err != nil { fmt.Println("Error reading message") return "", err } ra, err := ioutil.ReadAll(md.UnverifiedBody) if err != nil { return "", err } return string(ra), nil }