1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package engine
5
6import (
7	"errors"
8	"fmt"
9
10	"github.com/keybase/client/go/libkb"
11	keybase1 "github.com/keybase/client/go/protocol/keybase1"
12	"github.com/keybase/go-framed-msgpack-rpc/rpc"
13)
14
15type PGPPushPrivate struct {
16	arg keybase1.PGPPushPrivateArg
17}
18
19func (e *PGPPushPrivate) Name() string {
20	return "PGPPushPrivate"
21}
22
23func (e *PGPPushPrivate) Prereqs() Prereqs {
24	return Prereqs{}
25}
26
27func (e *PGPPushPrivate) RequiredUIs() []libkb.UIKind {
28	return []libkb.UIKind{}
29}
30
31func (e *PGPPushPrivate) SubConsumers() []libkb.UIConsumer {
32	return []libkb.UIConsumer{}
33}
34
35func NewPGPPushPrivate(arg keybase1.PGPPushPrivateArg) *PGPPushPrivate {
36	return &PGPPushPrivate{arg}
37}
38
39func getCurrentUserPGPKeys(m libkb.MetaContext) ([]libkb.PGPFingerprint, error) {
40	uid := m.CurrentUID()
41	if uid.IsNil() {
42		return nil, libkb.NewLoginRequiredError("for push/pull of PGP private keys to KBFS")
43	}
44	upk, _, err := m.G().GetUPAKLoader().LoadV2(libkb.NewLoadUserArgWithMetaContext(m).WithUID(uid))
45	if err != nil {
46		return nil, err
47	}
48	var res []libkb.PGPFingerprint
49	for _, key := range upk.Current.PGPKeys {
50		if key.Base.Revocation != nil {
51			continue
52		}
53		res = append(res, libkb.PGPFingerprint(key.Fingerprint))
54	}
55	if len(res) == 0 {
56		return nil, errors.New("The --all flag only works if you have PGP keys linked to your Keybase account")
57	}
58	return res, nil
59}
60
61func getPrivateFingerprints(m libkb.MetaContext, fps []keybase1.PGPFingerprint) ([]libkb.PGPFingerprint, error) {
62	if len(fps) == 0 {
63		return getCurrentUserPGPKeys(m)
64	}
65
66	var ret []libkb.PGPFingerprint
67	for _, fp := range fps {
68		ret = append(ret, libkb.ImportPGPFingerprint(fp))
69
70	}
71	return ret, nil
72}
73
74func simpleFSClient(m libkb.MetaContext) (*keybase1.SimpleFSClient, error) {
75	xp := m.G().ConnectionManager.LookupByClientType(keybase1.ClientType_KBFS)
76	if xp == nil {
77		return nil, libkb.KBFSNotRunningError{}
78	}
79	return &keybase1.SimpleFSClient{
80		Cli: rpc.NewClient(xp, libkb.NewContextifiedErrorUnwrapper(m.G()), nil),
81	}, nil
82}
83
84func (e *PGPPushPrivate) mkdir(m libkb.MetaContext, fs *keybase1.SimpleFSClient, path string) (err error) {
85	opid, err := fs.SimpleFSMakeOpid(m.Ctx())
86	if err != nil {
87		return err
88	}
89	defer fs.SimpleFSClose(m.Ctx(), opid)
90	err = fs.SimpleFSOpen(m.Ctx(), keybase1.SimpleFSOpenArg{
91		OpID:  opid,
92		Dest:  keybase1.NewPathWithKbfsPath(path),
93		Flags: keybase1.OpenFlags_DIRECTORY,
94	})
95	return err
96}
97
98func (e *PGPPushPrivate) write(m libkb.MetaContext, fs *keybase1.SimpleFSClient, path string, data string) (err error) {
99	opid, err := fs.SimpleFSMakeOpid(m.Ctx())
100	if err != nil {
101		return err
102	}
103	defer fs.SimpleFSClose(m.Ctx(), opid)
104	err = fs.SimpleFSOpen(m.Ctx(), keybase1.SimpleFSOpenArg{
105		OpID:  opid,
106		Dest:  keybase1.NewPathWithKbfsPath(path),
107		Flags: keybase1.OpenFlags_WRITE,
108	})
109	if err != nil {
110		return err
111	}
112	err = fs.SimpleFSWrite(m.Ctx(), keybase1.SimpleFSWriteArg{
113		OpID:    opid,
114		Offset:  0,
115		Content: []byte(data),
116	})
117	if err != nil {
118		return err
119	}
120	return nil
121}
122
123func (e *PGPPushPrivate) link(m libkb.MetaContext, fs *keybase1.SimpleFSClient, file string, link string) (err error) {
124	err = fs.SimpleFSSymlink(m.Ctx(), keybase1.SimpleFSSymlinkArg{
125		Target: file,
126		Link:   keybase1.NewPathWithKbfsPath(link),
127	})
128	return err
129}
130
131func (e *PGPPushPrivate) remove(m libkb.MetaContext, fs *keybase1.SimpleFSClient, file string) (err error) {
132	opid, err := fs.SimpleFSMakeOpid(m.Ctx())
133	if err != nil {
134		return err
135	}
136	defer fs.SimpleFSClose(m.Ctx(), opid)
137	err = fs.SimpleFSRemove(m.Ctx(), keybase1.SimpleFSRemoveArg{
138		OpID: opid,
139		Path: keybase1.NewPathWithKbfsPath(file),
140	})
141	if err != nil {
142		return err
143	}
144	err = fs.SimpleFSWait(m.Ctx(), opid)
145	return err
146}
147
148func (e *PGPPushPrivate) linkExists(m libkb.MetaContext, fs *keybase1.SimpleFSClient, file string) (exists bool, err error) {
149	defer m.Trace(fmt.Sprintf("linkExists(%q)", file), &err)()
150
151	var dirent keybase1.Dirent
152	dirent, err = fs.SimpleFSStat(m.Ctx(), keybase1.SimpleFSStatArg{
153		Path:                keybase1.NewPathWithKbfsPath(file),
154		RefreshSubscription: false,
155	})
156	if err != nil {
157		m.Debug("error accessing %s; assuming that the file doesn't exist (%s)", file, err.Error())
158		return false, nil
159	}
160	if dirent.DirentType != keybase1.DirentType_SYM {
161		return false, fmt.Errorf("Cannot rewrite %s since it's not a symlink", file)
162	}
163	return true, nil
164}
165
166func (e *PGPPushPrivate) push(m libkb.MetaContext, fp libkb.PGPFingerprint, tty string, fs *keybase1.SimpleFSClient) error {
167	armored, err := m.G().GetGpgClient().ImportKeyArmored(m, true /* secret */, fp, tty)
168	if err != nil {
169		return err
170	}
171
172	username := m.CurrentUsername()
173	if username.IsNil() {
174		return libkb.NewLoginRequiredError("no username found")
175	}
176
177	path := "/private/" + username.String() + "/.keys"
178
179	// Make /.keys/pgp. If it already exists, these mkdir calls should not error out.
180	err = e.mkdir(m, fs, path)
181	if err != nil {
182		return err
183	}
184	path += "/pgp"
185	err = e.mkdir(m, fs, path)
186	if err != nil {
187		return err
188	}
189
190	filename := fp.String() + "-" + fmt.Sprintf("%d", m.G().Clock().Now().Unix()) + ".asc"
191	linkname := fp.String() + ".asc"
192	filepath := path + "/" + filename
193	linkpath := path + "/" + linkname
194
195	err = e.write(m, fs, filepath, armored)
196	if err != nil {
197		return err
198	}
199
200	linkExists, err := e.linkExists(m, fs, linkpath)
201	if err != nil {
202		return err
203	}
204
205	// Note the small chance of a race here, but it doesn't seem dangerous.
206	if linkExists {
207		err = e.remove(m, fs, linkpath)
208		if err != nil {
209			return err
210		}
211	}
212
213	err = e.link(m, fs, filename, linkpath)
214	return err
215}
216
217func (e *PGPPushPrivate) Run(m libkb.MetaContext) (err error) {
218
219	defer m.Trace("PGPPushPrivate#Run", &err)()
220
221	tty, err := m.UIs().GPGUI.GetTTY(m.Ctx())
222	if err != nil {
223		return err
224	}
225
226	fingerprints, err := getPrivateFingerprints(m, e.arg.Fingerprints)
227	if err != nil {
228		return err
229	}
230
231	fs, err := simpleFSClient(m)
232	if err != nil {
233		return err
234	}
235
236	if len(fingerprints) == 0 {
237		return errors.New("no PGP keys provided")
238	}
239
240	for _, fp := range fingerprints {
241		err = e.push(m, fp, tty, fs)
242		if err != nil {
243			return err
244		}
245	}
246
247	return nil
248}
249