1/*
2Package pgp contains an implementation of the go.mozilla.org/sops/v3.MasterKey interface that encrypts and decrypts the
3data key by first trying with the golang.org/x/crypto/openpgp package and if that fails, by calling the "gpg" binary.
4*/
5package pgp //import "go.mozilla.org/sops/v3/pgp"
6
7import (
8	"bytes"
9	"encoding/hex"
10	"fmt"
11	"io/ioutil"
12	"net/http"
13	"os"
14	"os/user"
15	"path"
16	"strings"
17	"time"
18
19	"os/exec"
20
21	"github.com/howeyc/gopass"
22	"github.com/sirupsen/logrus"
23	gpgagent "go.mozilla.org/gopgagent"
24	"go.mozilla.org/sops/v3/logging"
25	"golang.org/x/crypto/openpgp"
26	"golang.org/x/crypto/openpgp/armor"
27)
28
29var log *logrus.Logger
30
31func init() {
32	log = logging.NewLogger("PGP")
33}
34
35// MasterKey is a PGP key used to securely store sops' data key by encrypting it and decrypting it
36type MasterKey struct {
37	Fingerprint  string
38	EncryptedKey string
39	CreationDate time.Time
40}
41
42// EncryptedDataKey returns the encrypted data key this master key holds
43func (key *MasterKey) EncryptedDataKey() []byte {
44	return []byte(key.EncryptedKey)
45}
46
47// SetEncryptedDataKey sets the encrypted data key for this master key
48func (key *MasterKey) SetEncryptedDataKey(enc []byte) {
49	key.EncryptedKey = string(enc)
50}
51
52func gpgBinary() string {
53	binary := "gpg"
54	if envBinary := os.Getenv("SOPS_GPG_EXEC"); envBinary != "" {
55		binary = envBinary
56	}
57	return binary
58}
59
60func (key *MasterKey) encryptWithGPGBinary(dataKey []byte) error {
61	fingerprint := key.Fingerprint
62	if offset := len(fingerprint) - 16; offset > 0 {
63		fingerprint = fingerprint[offset:]
64	}
65	args := []string{
66		"--no-default-recipient",
67		"--yes",
68		"--encrypt",
69		"-a",
70		"-r",
71		key.Fingerprint,
72		"--trusted-key",
73		fingerprint,
74		"--no-encrypt-to",
75	}
76	cmd := exec.Command(gpgBinary(), args...)
77	var stdout, stderr bytes.Buffer
78	cmd.Stdin = bytes.NewReader(dataKey)
79	cmd.Stdout = &stdout
80	cmd.Stderr = &stderr
81	err := cmd.Run()
82	if err != nil {
83		return err
84	}
85	key.EncryptedKey = stdout.String()
86	return nil
87}
88
89func getKeyFromKeyServer(fingerprint string) (openpgp.Entity, error) {
90	log.Warn("Deprecation Warning: GPG key fetching from a keyserver witihin sops will be removed in a future version of sops. See https://github.com/mozilla/sops/issues/727 for more information.")
91
92	url := fmt.Sprintf("https://keys.openpgp.org/vks/v1/by-fingerprint/%s", fingerprint)
93	resp, err := http.Get(url)
94	if err != nil {
95		return openpgp.Entity{}, fmt.Errorf("error getting key from keyserver: %s", err)
96	}
97	defer resp.Body.Close()
98	if resp.StatusCode != 200 {
99		return openpgp.Entity{}, fmt.Errorf("keyserver returned non-200 status code %s", resp.Status)
100	}
101	ents, err := openpgp.ReadArmoredKeyRing(resp.Body)
102	if err != nil {
103		return openpgp.Entity{}, fmt.Errorf("could not read entities: %s", err)
104	}
105	return *ents[0], nil
106}
107
108func (key *MasterKey) getPubKey() (openpgp.Entity, error) {
109	ring, err := key.pubRing()
110	if err == nil {
111		fingerprints := key.fingerprintMap(ring)
112		entity, ok := fingerprints[key.Fingerprint]
113		if ok {
114			return entity, nil
115		}
116	}
117	entity, err := getKeyFromKeyServer(key.Fingerprint)
118	if err != nil {
119		return openpgp.Entity{},
120			fmt.Errorf("key with fingerprint %s is not available "+
121				"in keyring and could not be retrieved from keyserver", key.Fingerprint)
122	}
123	return entity, nil
124}
125
126func (key *MasterKey) encryptWithCryptoOpenPGP(dataKey []byte) error {
127	entity, err := key.getPubKey()
128	if err != nil {
129		return err
130	}
131	encbuf := new(bytes.Buffer)
132	armorbuf, err := armor.Encode(encbuf, "PGP MESSAGE", nil)
133	if err != nil {
134		return err
135	}
136	plaintextbuf, err := openpgp.Encrypt(armorbuf, []*openpgp.Entity{&entity}, nil, &openpgp.FileHints{IsBinary: true}, nil)
137	if err != nil {
138		return err
139	}
140	_, err = plaintextbuf.Write(dataKey)
141	if err != nil {
142		return err
143	}
144	err = plaintextbuf.Close()
145	if err != nil {
146		return err
147	}
148	err = armorbuf.Close()
149	if err != nil {
150		return err
151	}
152	bytes, err := ioutil.ReadAll(encbuf)
153	if err != nil {
154		return err
155	}
156	key.EncryptedKey = string(bytes)
157	return nil
158}
159
160// Encrypt encrypts the data key with the PGP key with the same fingerprint as the MasterKey. It looks for PGP public keys in $PGPHOME/pubring.gpg.
161func (key *MasterKey) Encrypt(dataKey []byte) error {
162	openpgpErr := key.encryptWithCryptoOpenPGP(dataKey)
163	if openpgpErr == nil {
164		log.WithField("fingerprint", key.Fingerprint).Info("Encryption succeeded")
165		return nil
166	}
167	binaryErr := key.encryptWithGPGBinary(dataKey)
168	if binaryErr == nil {
169		log.WithField("fingerprint", key.Fingerprint).Info("Encryption succeeded")
170		return nil
171	}
172	log.WithField("fingerprint", key.Fingerprint).Info("Encryption failed")
173	return fmt.Errorf(
174		`could not encrypt data key with PGP key: golang.org/x/crypto/openpgp error: %v; GPG binary error: %v`,
175		openpgpErr, binaryErr)
176}
177
178// EncryptIfNeeded encrypts the data key with PGP only if it's needed, that is, if it hasn't been encrypted already
179func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error {
180	if key.EncryptedKey == "" {
181		return key.Encrypt(dataKey)
182	}
183	return nil
184}
185
186func (key *MasterKey) decryptWithGPGBinary() ([]byte, error) {
187	args := []string{
188		"--use-agent",
189		"-d",
190	}
191	cmd := exec.Command(gpgBinary(), args...)
192	var stdout, stderr bytes.Buffer
193	cmd.Stdin = strings.NewReader(key.EncryptedKey)
194	cmd.Stdout = &stdout
195	cmd.Stderr = &stderr
196	err := cmd.Run()
197	if err != nil {
198		return nil, err
199	}
200	return stdout.Bytes(), nil
201}
202
203func (key *MasterKey) decryptWithCryptoOpenpgp() ([]byte, error) {
204	ring, err := key.secRing()
205	if err != nil {
206		return nil, fmt.Errorf("Could not load secring: %s", err)
207	}
208	block, err := armor.Decode(strings.NewReader(key.EncryptedKey))
209	if err != nil {
210		return nil, fmt.Errorf("Armor decoding failed: %s", err)
211	}
212	md, err := openpgp.ReadMessage(block.Body, ring, key.passphrasePrompt(), nil)
213	if err != nil {
214		return nil, fmt.Errorf("Reading PGP message failed: %s", err)
215	}
216	if b, err := ioutil.ReadAll(md.UnverifiedBody); err == nil {
217		return b, nil
218	}
219	return nil, fmt.Errorf("The key could not be decrypted with any of the PGP entries")
220}
221
222// Decrypt uses PGP to obtain the data key from the EncryptedKey store in the MasterKey and returns it
223func (key *MasterKey) Decrypt() ([]byte, error) {
224	dataKey, openpgpErr := key.decryptWithCryptoOpenpgp()
225	if openpgpErr == nil {
226		log.WithField("fingerprint", key.Fingerprint).Info("Decryption succeeded")
227		return dataKey, nil
228	}
229	dataKey, binaryErr := key.decryptWithGPGBinary()
230	if binaryErr == nil {
231		log.WithField("fingerprint", key.Fingerprint).Info("Decryption succeeded")
232		return dataKey, nil
233	}
234	log.WithField("fingerprint", key.Fingerprint).Info("Decryption failed")
235	return nil, fmt.Errorf(
236		`could not decrypt data key with PGP key: golang.org/x/crypto/openpgp error: %v; GPG binary error: %v`,
237		openpgpErr, binaryErr)
238}
239
240// NeedsRotation returns whether the data key needs to be rotated or not
241func (key *MasterKey) NeedsRotation() bool {
242	return time.Since(key.CreationDate).Hours() > 24*30*6
243}
244
245// ToString returns the string representation of the key, i.e. its fingerprint
246func (key *MasterKey) ToString() string {
247	return key.Fingerprint
248}
249
250func (key *MasterKey) gpgHome() string {
251	dir := os.Getenv("GNUPGHOME")
252	if dir == "" {
253		usr, err := user.Current()
254		if err != nil {
255			return path.Join(os.Getenv("HOME"), "/.gnupg")
256		}
257		return path.Join(usr.HomeDir, ".gnupg")
258	}
259	return dir
260}
261
262// NewMasterKeyFromFingerprint takes a PGP fingerprint and returns a new MasterKey with that fingerprint
263func NewMasterKeyFromFingerprint(fingerprint string) *MasterKey {
264	return &MasterKey{
265		Fingerprint:  strings.Replace(fingerprint, " ", "", -1),
266		CreationDate: time.Now().UTC(),
267	}
268}
269
270// MasterKeysFromFingerprintString takes a comma separated list of PGP fingerprints and returns a slice of new MasterKeys with those fingerprints
271func MasterKeysFromFingerprintString(fingerprint string) []*MasterKey {
272	var keys []*MasterKey
273	if fingerprint == "" {
274		return keys
275	}
276	for _, s := range strings.Split(fingerprint, ",") {
277		keys = append(keys, NewMasterKeyFromFingerprint(s))
278	}
279	return keys
280}
281
282func (key *MasterKey) loadRing(path string) (openpgp.EntityList, error) {
283	f, err := os.Open(path)
284	if err != nil {
285		return openpgp.EntityList{}, err
286	}
287	defer f.Close()
288	keyring, err := openpgp.ReadKeyRing(f)
289	if err != nil {
290		return keyring, err
291	}
292	return keyring, nil
293}
294
295func (key *MasterKey) secRing() (openpgp.EntityList, error) {
296	return key.loadRing(key.gpgHome() + "/secring.gpg")
297}
298
299func (key *MasterKey) pubRing() (openpgp.EntityList, error) {
300	return key.loadRing(key.gpgHome() + "/pubring.gpg")
301}
302
303func (key *MasterKey) fingerprintMap(ring openpgp.EntityList) map[string]openpgp.Entity {
304	fps := make(map[string]openpgp.Entity)
305	for _, entity := range ring {
306		fp := strings.ToUpper(hex.EncodeToString(entity.PrimaryKey.Fingerprint[:]))
307		if entity != nil {
308			fps[fp] = *entity
309		}
310	}
311	return fps
312}
313
314func (key *MasterKey) passphrasePrompt() func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
315	callCounter := 0
316	maxCalls := 3
317	return func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
318		if callCounter >= maxCalls {
319			return nil, fmt.Errorf("function passphrasePrompt called too many times")
320		}
321		callCounter++
322
323		conn, err := gpgagent.NewConn()
324		if err == gpgagent.ErrNoAgent {
325			log.Infof("gpg-agent not found, continuing with manual passphrase " +
326				"input...")
327			fmt.Print("Enter PGP key passphrase: ")
328			pass, err := gopass.GetPasswd()
329			if err != nil {
330				return nil, err
331			}
332			for _, k := range keys {
333				k.PrivateKey.Decrypt(pass)
334			}
335			return pass, err
336		}
337		if err != nil {
338			return nil, fmt.Errorf("Could not establish connection with gpg-agent: %s", err)
339		}
340		defer conn.Close()
341		for _, k := range keys {
342			req := gpgagent.PassphraseRequest{
343				CacheKey: k.PublicKey.KeyIdShortString(),
344				Prompt:   "Passphrase",
345				Desc:     fmt.Sprintf("Unlock key %s to decrypt sops's key", k.PublicKey.KeyIdShortString()),
346			}
347			pass, err := conn.GetPassphrase(&req)
348			if err != nil {
349				return nil, fmt.Errorf("gpg-agent passphrase request errored: %s", err)
350			}
351			k.PrivateKey.Decrypt([]byte(pass))
352			return []byte(pass), nil
353		}
354		return nil, fmt.Errorf("No key to unlock")
355	}
356}
357
358// ToMap converts the MasterKey into a map for serialization purposes
359func (key MasterKey) ToMap() map[string]interface{} {
360	out := make(map[string]interface{})
361	out["fp"] = key.Fingerprint
362	out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339)
363	out["enc"] = key.EncryptedKey
364	return out
365}
366