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	"io"
9
10	"github.com/keybase/client/go/libkb"
11	keybase1 "github.com/keybase/client/go/protocol/keybase1"
12	"github.com/keybase/go-crypto/openpgp/armor"
13)
14
15type PGPEncryptArg struct {
16	Recips       []string // user assertions
17	Source       io.Reader
18	Sink         io.WriteCloser
19	NoSign       bool
20	NoSelf       bool
21	BinaryOutput bool
22	KeyQuery     string
23}
24
25// PGPEncrypt encrypts data read from a source into a sink
26// for a set of users.  It will track them if necessary.
27type PGPEncrypt struct {
28	arg      *PGPEncryptArg
29	me       *libkb.User
30	warnings libkb.HashSecurityWarnings
31	libkb.Contextified
32}
33
34// NewPGPEncrypt creates a PGPEncrypt engine.
35func NewPGPEncrypt(g *libkb.GlobalContext, arg *PGPEncryptArg) *PGPEncrypt {
36	return &PGPEncrypt{
37		arg:          arg,
38		Contextified: libkb.NewContextified(g),
39	}
40}
41
42// Name is the unique engine name.
43func (e *PGPEncrypt) Name() string {
44	return "PGPEncrypt"
45}
46
47// GetPrereqs returns the engine prereqs.
48func (e *PGPEncrypt) Prereqs() Prereqs {
49	return Prereqs{}
50}
51
52// RequiredUIs returns the required UIs.
53func (e *PGPEncrypt) RequiredUIs() []libkb.UIKind {
54	// context.SecretKeyPromptArg requires SecretUI
55	return []libkb.UIKind{
56		libkb.SecretUIKind,
57		libkb.PgpUIKind,
58	}
59}
60
61// SubConsumers returns the other UI consumers for this engine.
62func (e *PGPEncrypt) SubConsumers() []libkb.UIConsumer {
63	return []libkb.UIConsumer{
64		&PGPKeyfinder{},
65		&ResolveThenIdentify2{},
66	}
67}
68
69// Run starts the engine.
70func (e *PGPEncrypt) Run(m libkb.MetaContext) error {
71	// verify valid options based on logged in state:
72	ok, uid := isLoggedIn(m)
73
74	if !ok {
75		// not logged in.  this is fine, unless they requested signing the message.
76		if !e.arg.NoSign {
77			return libkb.LoginRequiredError{Context: "you must be logged in to sign"}
78		}
79
80		// or trying to encrypt for self
81		if !e.arg.NoSelf {
82			return libkb.LoginRequiredError{Context: "you must be logged in to encrypt for yourself (or use --no-self flag)"}
83		}
84	} else {
85		me, err := libkb.LoadMeByMetaContextAndUID(m, uid)
86		if err != nil {
87			return err
88		}
89		e.me = me
90	}
91
92	var mykey *libkb.PGPKeyBundle
93	var signer *libkb.PGPKeyBundle
94	if !e.arg.NoSign {
95		ska := libkb.SecretKeyArg{
96			Me:       e.me,
97			KeyType:  libkb.PGPKeyType,
98			KeyQuery: e.arg.KeyQuery,
99		}
100		key, err := e.G().Keyrings.GetSecretKeyWithPrompt(m, m.SecretKeyPromptArg(ska, "command-line signature"))
101		if err != nil {
102			return err
103		}
104
105		var ok bool
106		mykey, ok = key.(*libkb.PGPKeyBundle)
107		if !ok {
108			return errors.New("Can only sign with PGP keys")
109		}
110		signer = mykey
111	}
112
113	usernames, err := e.verifyUsers(m, e.arg.Recips, ok)
114	if err != nil {
115		return err
116	}
117
118	kfarg := &PGPKeyfinderArg{
119		Usernames: usernames,
120	}
121
122	kf := NewPGPKeyfinder(e.G(), kfarg)
123	if err := RunEngine2(m, kf); err != nil {
124		return err
125	}
126	uplus := kf.UsersPlusKeys()
127
128	var writer io.WriteCloser
129	if e.arg.BinaryOutput {
130		writer = e.arg.Sink
131	} else {
132		aw, err := armor.Encode(e.arg.Sink, "PGP MESSAGE", libkb.PGPArmorHeaders)
133		if err != nil {
134			return err
135		}
136		writer = aw
137	}
138
139	ks := newKeyset()
140	e.warnings = libkb.HashSecurityWarnings{}
141
142	if mykey != nil {
143		if w := mykey.SecurityWarnings(
144			libkb.HashSecurityWarningOurIdentityHash,
145		); len(w) > 0 {
146			e.warnings = append(e.warnings, w...)
147		}
148	}
149
150	for _, up := range uplus {
151		for _, k := range up.Keys {
152			if len(k.Entity.Revocations)+len(k.Entity.UnverifiedRevocations) > 0 {
153				continue
154			}
155
156			if w := k.SecurityWarnings(
157				libkb.HashSecurityWarningRecipientsIdentityHash,
158			); len(w) > 0 {
159				e.warnings = append(e.warnings, w...)
160			}
161
162			ks.Add(k)
163		}
164	}
165
166	if len(e.arg.Recips) > 0 && len(ks.keys) == 0 {
167		return errors.New("Cannot encrypt - recipient does not have a non-revoked key.")
168	}
169
170	if !e.arg.NoSelf {
171		if mykey == nil {
172			// need to load the public key for the logged in user
173			mykey, err = e.loadSelfKey()
174			if err != nil {
175				return err
176			}
177		}
178
179		// mykey could still be nil
180		if mykey != nil {
181			ks.Add(mykey)
182		}
183	}
184
185	for _, warning := range e.warnings.Strings() {
186		if err := m.UIs().PgpUI.OutputPGPWarning(m.Ctx(), keybase1.OutputPGPWarningArg{
187			Warning: warning,
188		}); err != nil {
189			return err
190		}
191	}
192
193	recipients := ks.Sorted()
194	if err := libkb.PGPEncrypt(e.arg.Source, writer, signer, recipients); err != nil {
195		return err
196	}
197	if !e.arg.BinaryOutput {
198		return e.arg.Sink.Close()
199	}
200	return nil
201}
202
203func (e *PGPEncrypt) loadSelfKey() (*libkb.PGPKeyBundle, error) {
204	me, err := libkb.LoadMe(libkb.NewLoadUserArg(e.G()))
205	if err != nil {
206		return nil, err
207	}
208
209	keys := me.FilterActivePGPKeys(true, e.arg.KeyQuery)
210	if len(keys) == 0 {
211		return nil, libkb.NoKeyError{Msg: "No PGP key found for encrypting for self (add a PGP key or use --no-self flag)"}
212	}
213	return keys[0], nil
214}
215
216func (e *PGPEncrypt) verifyUsers(m libkb.MetaContext, assertions []string, loggedIn bool) ([]string, error) {
217	var names []string
218	for _, userAssert := range assertions {
219		arg := keybase1.Identify2Arg{
220			UserAssertion: userAssert,
221			Reason: keybase1.IdentifyReason{
222				Type: keybase1.IdentifyReasonType_ENCRYPT,
223			},
224			AlwaysBlock:      true,
225			IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
226		}
227		eng := NewResolveThenIdentify2(e.G(), &arg)
228		if err := RunEngine2(m, eng); err != nil {
229			return nil, libkb.IdentifyFailedError{Assertion: userAssert, Reason: err.Error()}
230		}
231		res, err := eng.Result(m)
232		if err != nil {
233			return nil, err
234		}
235		names = append(names, res.Upk.GetName())
236	}
237	return names, nil
238}
239
240// keyset maintains a set of pgp keys, preserving insertion order.
241type keyset struct {
242	index []keybase1.KID
243	keys  map[keybase1.KID]*libkb.PGPKeyBundle
244}
245
246// newKeyset creates an empty keyset.
247func newKeyset() *keyset {
248	return &keyset{keys: make(map[keybase1.KID]*libkb.PGPKeyBundle)}
249}
250
251// Add adds bundle to the keyset.  If a key already exists, it
252// will be ignored.
253func (k *keyset) Add(bundle *libkb.PGPKeyBundle) {
254	kid := bundle.GetKID()
255	if _, ok := k.keys[kid]; ok {
256		return
257	}
258	k.keys[kid] = bundle
259	k.index = append(k.index, kid)
260}
261
262// Sorted returns the unique keys in insertion order.
263func (k *keyset) Sorted() []*libkb.PGPKeyBundle {
264	var sorted []*libkb.PGPKeyBundle
265	for _, kid := range k.index {
266		sorted = append(sorted, k.keys[kid])
267	}
268	return sorted
269}
270