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
6//
7// engine.GPGImportKeyEngine is a class that selects key from the GPG keyring via
8// shell-out to the gpg command line client. It's useful in `client mykey select`
9// and other places in which the user picks existing PGP keys on the existing
10// system for use in Keybase tasks.
11//
12
13import (
14	"fmt"
15
16	"github.com/keybase/client/go/libkb"
17	keybase1 "github.com/keybase/client/go/protocol/keybase1"
18)
19
20type GPGImportKeyArg struct {
21	Query                string
22	Signer               libkb.GenericKey
23	AllowMulti           bool
24	SkipImport           bool
25	OnlyImport           bool
26	HasProvisionedDevice bool
27	Me                   *libkb.User
28	Lks                  *libkb.LKSec
29}
30
31type GPGImportKeyEngine struct {
32	last                   *libkb.PGPKeyBundle
33	arg                    *GPGImportKeyArg
34	duplicatedFingerprints []libkb.PGPFingerprint
35	libkb.Contextified
36}
37
38func NewGPGImportKeyEngine(g *libkb.GlobalContext, arg *GPGImportKeyArg) *GPGImportKeyEngine {
39	return &GPGImportKeyEngine{
40		arg:          arg,
41		Contextified: libkb.NewContextified(g),
42	}
43}
44
45func (e *GPGImportKeyEngine) Prereqs() Prereqs {
46	if !e.arg.HasProvisionedDevice {
47		return Prereqs{TemporarySession: true}
48	}
49	return Prereqs{Device: true}
50}
51
52func (e *GPGImportKeyEngine) Name() string {
53	return "GPGImportKeyEngine"
54}
55
56func (e *GPGImportKeyEngine) RequiredUIs() []libkb.UIKind {
57	return []libkb.UIKind{
58		libkb.GPGUIKind,
59		libkb.SecretUIKind,
60	}
61}
62
63func (e *GPGImportKeyEngine) SubConsumers() []libkb.UIConsumer {
64	return []libkb.UIConsumer{
65		&PGPKeyImportEngine{},
66		&PGPUpdateEngine{},
67	}
68}
69
70func (e *GPGImportKeyEngine) WantsGPG(mctx libkb.MetaContext) (bool, error) {
71	gpg := e.G().GetGpgClient()
72	canExec, err := gpg.CanExec(mctx)
73	if err != nil {
74		return false, err
75	}
76	if !canExec {
77		return false, nil
78	}
79
80	// they have gpg
81
82	// get an index of all the secret keys
83	index, _, err := gpg.Index(mctx, true, "")
84	if err != nil {
85		return false, err
86	}
87	if index.Len() == 0 {
88		// no private keys available, so don't offer
89		return false, nil
90	}
91
92	res, err := mctx.UIs().GPGUI.WantToAddGPGKey(mctx.Ctx(), 0)
93	if err != nil {
94		return false, err
95	}
96	return res, nil
97}
98
99func (e *GPGImportKeyEngine) Run(mctx libkb.MetaContext) (err error) {
100	gpg := e.G().GetGpgClient()
101
102	me := e.arg.Me
103	if me == nil {
104		if me, err = libkb.LoadMe(libkb.NewLoadUserPubOptionalArg(e.G())); err != nil {
105			return err
106		}
107	}
108
109	if !e.arg.OnlyImport {
110		if err = PGPCheckMulti(me, e.arg.AllowMulti); err != nil {
111			return err
112		}
113	}
114
115	if err = gpg.Configure(mctx); err != nil {
116		return err
117	}
118	index, warns, err := gpg.Index(mctx, true, e.arg.Query)
119	if err != nil {
120		return err
121	}
122	warns.Warn(e.G())
123
124	var gks []keybase1.GPGKey
125	for _, key := range index.Keys {
126		gk := keybase1.GPGKey{
127			Algorithm:  fmt.Sprintf("%d%s", key.Bits, key.AlgoString()),
128			KeyID:      key.GetFingerprint().ToKeyID(),
129			Expiration: key.ExpirationString(),
130			Identities: key.GetPGPIdentities(),
131		}
132		gks = append(gks, gk)
133	}
134
135	if len(gks) == 0 {
136		return fmt.Errorf("No PGP keys available to choose from.")
137	}
138
139	res, err := mctx.UIs().GPGUI.SelectKeyAndPushOption(mctx.Ctx(), keybase1.SelectKeyAndPushOptionArg{Keys: gks})
140	if err != nil {
141		return err
142	}
143	mctx.Debug("SelectKey result: %+v", res)
144
145	var selected *libkb.GpgPrimaryKey
146	for _, key := range index.Keys {
147		if key.GetFingerprint().ToKeyID() == res.KeyID {
148			selected = key
149			break
150		}
151	}
152
153	if selected == nil {
154		return nil
155	}
156
157	publicKeys := me.GetActivePGPKeys(false)
158	duplicate := false
159	for _, key := range publicKeys {
160		if key.GetFingerprint().Eq(*(selected.GetFingerprint())) {
161			duplicate = true
162			break
163		}
164	}
165	if duplicate && !e.arg.OnlyImport {
166		// This key's already been posted to the server.
167		res, err := mctx.UIs().GPGUI.ConfirmDuplicateKeyChosen(mctx.Ctx(), 0)
168		if err != nil {
169			return err
170		}
171		if !res {
172			return libkb.SibkeyAlreadyExistsError{}
173		}
174		// We're sending a key update, then.
175		fp := selected.GetFingerprint().String()
176		eng := NewPGPUpdateEngine(e.G(), []string{fp}, false)
177		err = RunEngine2(mctx, eng)
178		e.duplicatedFingerprints = eng.duplicatedFingerprints
179
180		if err != nil {
181			return err
182		}
183
184		if !e.arg.SkipImport {
185			// Key is duplicate, but caller wants to import secret
186			// half.
187			res, err := mctx.UIs().GPGUI.ConfirmImportSecretToExistingKey(mctx.Ctx(), 0)
188			if err != nil {
189				return err
190			}
191			if !res {
192				// But update itself has finished, so this
193				// cancellation is not an error.
194				mctx.Info("User cancelled secret key import.")
195				return nil
196			}
197			// Fall through with OnlyImport=true so it skips sig posting
198			// (which would be rejected because of duplicate kid).
199			e.arg.OnlyImport = true
200		} else {
201			// Nothing to more do.
202			return nil
203		}
204	}
205
206	tty, err := mctx.UIs().GPGUI.GetTTY(mctx.Ctx())
207	if err != nil {
208		mctx.Warning("error getting TTY for GPG: %s", err)
209		err = nil
210	}
211
212	var bundle *libkb.PGPKeyBundle
213
214	if e.arg.SkipImport {
215		// If we don't need secret key to save in Keybase keyring,
216		// just import public key and rely on GPG fallback for reverse
217		// signature.
218		bundle, err = gpg.ImportKey(mctx, false, *(selected.GetFingerprint()), tty)
219		if err != nil {
220			return fmt.Errorf("ImportKey (secret: false) error: %s", err)
221		}
222	} else {
223		bundle, err = gpg.ImportKey(mctx, true, *(selected.GetFingerprint()), tty)
224		if err != nil {
225			return fmt.Errorf("ImportKey (secret: true) error: %s", err)
226		}
227
228		if err := bundle.Unlock(mctx, "Import of key into Keybase keyring", mctx.UIs().SecretUI); err != nil {
229			return err
230		}
231
232		if !libkb.FindPGPPrivateKey(bundle) {
233			return PGPImportStubbedError{KeyIDString: selected.GetFingerprint().ToKeyID()}
234		}
235	}
236
237	if e.arg.OnlyImport {
238		if err := e.ensurePublicPartIsPublished(me, bundle.GetKID()); err != nil {
239			// Make sure key is active in user's sigchain. Otherwise,
240			// after importing to local keychain, Keybase will refuse
241			// to use it for any operation anyway.
242			return err
243		}
244	}
245
246	mctx.Debug("Bundle unlocked: %s", selected.GetFingerprint().ToKeyID())
247
248	eng := NewPGPKeyImportEngine(mctx.G(), PGPKeyImportEngineArg{
249		Pregen:      bundle,
250		SigningKey:  e.arg.Signer,
251		Me:          me,
252		AllowMulti:  e.arg.AllowMulti,
253		NoSave:      e.arg.SkipImport,
254		OnlySave:    e.arg.OnlyImport,
255		Lks:         e.arg.Lks,
256		GPGFallback: true,
257	})
258
259	if err = RunEngine2(mctx, eng); err != nil {
260
261		// It's important to propagate a CanceledError unmolested,
262		// since the UI needs to know that. See:
263		//  https://github.com/keybase/client/issues/226
264		if _, ok := err.(libkb.CanceledError); !ok {
265			err = libkb.KeyGenError{Msg: err.Error()}
266		}
267		return
268	}
269
270	mctx.Debug("Key %s imported", selected.GetFingerprint().ToKeyID())
271
272	e.last = bundle
273
274	return nil
275}
276
277func (e *GPGImportKeyEngine) LastKey() *libkb.PGPKeyBundle {
278	return e.last
279}
280
281func (e *GPGImportKeyEngine) ensurePublicPartIsPublished(me *libkb.User, kid keybase1.KID) error {
282	ckf := me.GetComputedKeyFamily()
283	if ckf == nil {
284		return fmt.Errorf("cannot get ComputedKeyFamily")
285	}
286	active := ckf.GetKeyRole(kid)
287	if active != libkb.DLGSibkey {
288		return PGPNotActiveForLocalImport{kid}
289	}
290	return nil
291}
292