1package pgpkeys
2
3import (
4	"bytes"
5	"encoding/base64"
6	"errors"
7	"fmt"
8	"os"
9	"strings"
10
11	"github.com/hashicorp/errwrap"
12	"github.com/keybase/go-crypto/openpgp"
13)
14
15// PubKeyFileFlag implements flag.Value and command.Example to receive exactly
16// one PGP or keybase key via a flag.
17type PubKeyFileFlag string
18
19func (p *PubKeyFileFlag) String() string { return string(*p) }
20
21func (p *PubKeyFileFlag) Set(val string) error {
22	if p != nil && *p != "" {
23		return errors.New("can only be specified once")
24	}
25
26	keys, err := ParsePGPKeys(strings.Split(val, ","))
27	if err != nil {
28		return err
29	}
30
31	if len(keys) > 1 {
32		return errors.New("can only specify one pgp key")
33	}
34
35	*p = PubKeyFileFlag(keys[0])
36	return nil
37}
38
39func (p *PubKeyFileFlag) Example() string { return "keybase:user" }
40
41// PGPPubKeyFiles implements the flag.Value interface and allows parsing and
42// reading a list of PGP public key files.
43type PubKeyFilesFlag []string
44
45func (p *PubKeyFilesFlag) String() string {
46	return fmt.Sprint(*p)
47}
48
49func (p *PubKeyFilesFlag) Set(val string) error {
50	if len(*p) > 0 {
51		return errors.New("can only be specified once")
52	}
53
54	keys, err := ParsePGPKeys(strings.Split(val, ","))
55	if err != nil {
56		return err
57	}
58
59	*p = PubKeyFilesFlag(keys)
60	return nil
61}
62
63func (p *PubKeyFilesFlag) Example() string { return "keybase:user1, keybase:user2, ..." }
64
65// ParsePGPKeys takes a list of PGP keys and parses them either using keybase
66// or reading them from disk and returns the "expanded" list of pgp keys in
67// the same order.
68func ParsePGPKeys(keyfiles []string) ([]string, error) {
69	keys := make([]string, len(keyfiles))
70
71	keybaseMap, err := FetchKeybasePubkeys(keyfiles)
72	if err != nil {
73		return nil, err
74	}
75
76	for i, keyfile := range keyfiles {
77		keyfile = strings.TrimSpace(keyfile)
78
79		if strings.HasPrefix(keyfile, kbPrefix) {
80			key, ok := keybaseMap[keyfile]
81			if !ok || key == "" {
82				return nil, fmt.Errorf("keybase user %q not found", strings.TrimPrefix(keyfile, kbPrefix))
83			}
84			keys[i] = key
85			continue
86		}
87
88		pgpStr, err := ReadPGPFile(keyfile)
89		if err != nil {
90			return nil, err
91		}
92		keys[i] = pgpStr
93	}
94
95	return keys, nil
96}
97
98// ReadPGPFile reads the given PGP file from disk.
99func ReadPGPFile(path string) (string, error) {
100	if len(path) <= 0 {
101		return "", errors.New("empty path")
102	}
103	if path[0] == '@' {
104		path = path[1:]
105	}
106	f, err := os.Open(path)
107	if err != nil {
108		return "", err
109	}
110	defer f.Close()
111	buf := bytes.NewBuffer(nil)
112	_, err = buf.ReadFrom(f)
113	if err != nil {
114		return "", err
115	}
116
117	// First parse as an armored keyring file, if that doesn't work, treat it as a straight binary/b64 string
118	keyReader := bytes.NewReader(buf.Bytes())
119	entityList, err := openpgp.ReadArmoredKeyRing(keyReader)
120	if err == nil {
121		if len(entityList) != 1 {
122			return "", fmt.Errorf("more than one key found in file %q", path)
123		}
124		if entityList[0] == nil {
125			return "", fmt.Errorf("primary key was nil for file %q", path)
126		}
127
128		serializedEntity := bytes.NewBuffer(nil)
129		err = entityList[0].Serialize(serializedEntity)
130		if err != nil {
131			return "", errwrap.Wrapf(fmt.Sprintf("error serializing entity for file %q: {{err}}", path), err)
132		}
133
134		return base64.StdEncoding.EncodeToString(serializedEntity.Bytes()), nil
135	}
136
137	_, err = base64.StdEncoding.DecodeString(buf.String())
138	if err == nil {
139		return buf.String(), nil
140	}
141	return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
142
143}
144