package gpass 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, error) { ret := &Store{} u, err := user.Current() if err != nil { return ret, fmt.Errorf("Can't get current user.") } ret.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 ret, fmt.Errorf("Can't open keyring file") } kr, err := openpgp.ReadKeyRing(fd) if err != nil { return ret, fmt.Errorf("Can't open gnupg keyring.") } ret.keyring = kr return ret, 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 err error var passphrase []byte dec := func(p *packet.PrivateKey) { for i := 0; i<3; i++ { if err = p.Decrypt(passphrase); err == nil { break } passphrase = prompt() } } 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 { dec(p) } for _, s := range k.Entity.Subkeys { if p := s.PrivateKey; p != nil && p.Encrypted { dec(p) } } } } return passphrase, err } 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") } 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 }