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)
13
14type PaperProvisionEngine struct {
15	libkb.Contextified
16	Username       string
17	DeviceName     string
18	PaperKey       string
19	result         error
20	lks            *libkb.LKSec
21	User           *libkb.User
22	perUserKeyring *libkb.PerUserKeyring
23
24	deviceWrapEng *DeviceWrap
25}
26
27func NewPaperProvisionEngine(g *libkb.GlobalContext, username, deviceName,
28	paperKey string) *PaperProvisionEngine {
29	return &PaperProvisionEngine{
30		Contextified: libkb.NewContextified(g),
31		Username:     username,
32		DeviceName:   deviceName,
33		PaperKey:     paperKey,
34	}
35}
36
37func (e *PaperProvisionEngine) Name() string {
38	return "PaperProvision"
39}
40
41func (e *PaperProvisionEngine) Prereqs() Prereqs {
42	return Prereqs{}
43}
44
45func (e *PaperProvisionEngine) RequiredUIs() []libkb.UIKind {
46	return []libkb.UIKind{
47		libkb.ProvisionUIKind,
48		libkb.LogUIKind,
49		libkb.SecretUIKind,
50		libkb.LoginUIKind,
51	}
52}
53
54func (e *PaperProvisionEngine) Run(m libkb.MetaContext) (err error) {
55	defer m.Trace("PaperProvisionEngine#Run", &err)()
56
57	// clear out any existing session
58	err = m.LogoutKeepSecrets()
59	if err != nil {
60		m.Debug("error on logout: %+v", err)
61	}
62
63	m = m.WithNewProvisionalLoginContext()
64
65	// From this point on, if there's an error, we abort the
66	// transaction.
67	defer func() {
68		if err == nil {
69			m = m.CommitProvisionalLogin()
70		}
71	}()
72
73	// run the LoginLoadUser sub-engine to load a user
74	ueng := newLoginLoadUser(e.G(), e.Username)
75	if err = RunEngine2(m, ueng); err != nil {
76		return err
77	}
78
79	// make sure the user isn't already provisioned (can
80	// get here if usernameOrEmail is an email address
81	// for an already provisioned on this device user).
82	if ueng.User().HasCurrentDeviceInCurrentInstall() {
83		return libkb.DeviceAlreadyProvisionedError{}
84	}
85	e.User = ueng.User()
86
87	// Transform the paper key phrase into a key pair
88	bkarg := &PaperKeyGenArg{
89		Passphrase: libkb.PaperKeyPhrase(e.PaperKey),
90		SkipPush:   true,
91	}
92	bkeng := NewPaperKeyGen(e.G(), bkarg)
93	if err := RunEngine2(m, bkeng); err != nil {
94		return err
95	}
96
97	keys := bkeng.DeviceWithKeys()
98
99	// Make sure the key matches the logged in user
100	// use the KID to find the uid
101	uid, err := keys.Populate(m)
102	if err != nil {
103		return err
104	}
105
106	if uid.NotEqual(e.User.GetUID()) {
107		e.G().Log.Debug("paper key entered was for a different user")
108		return fmt.Errorf("paper key valid, but for %s, not %s", uid, e.User.GetUID())
109	}
110
111	e.perUserKeyring, err = libkb.NewPerUserKeyring(e.G(), e.User.GetUID())
112	if err != nil {
113		return err
114	}
115
116	// Make new device keys and sign them with this paper key
117	if err = e.paper(m, keys); err != nil {
118		return err
119	}
120
121	// Finish provisoning by calling SwitchConfigAndActiveDevice. we
122	// can't undo that, so do not error out after that.
123	if err := e.deviceWrapEng.SwitchConfigAndActiveDevice(m); err != nil {
124		return err
125	}
126
127	e.sendNotification(m)
128	return nil
129
130}
131
132// copied more or less from loginProvision.paper()
133func (e *PaperProvisionEngine) paper(m libkb.MetaContext, keys *libkb.DeviceWithKeys) error {
134	// After obtaining login session, this will be called before the login state is released.
135	// It signs this new device with the paper key.
136	u := e.User
137	nn := u.GetNormalizedName()
138	uv := u.ToUserVersion()
139
140	// Set the active device to be a special paper key active device, which keeps
141	// a cached copy around for DeviceKeyGen, which requires it to be in memory.
142	// It also will establish a NIST so that API calls can proceed on behalf of the user.
143	m = m.WithProvisioningKeyActiveDevice(keys, uv)
144	if err := m.LoginContext().SetUsernameUserVersion(nn, uv); err != nil {
145		return err
146	}
147
148	// need lksec to store device keys locally
149	if err := e.fetchLKS(m, keys.EncryptionKey()); err != nil {
150		return err
151	}
152
153	if err := e.makeDeviceKeysWithSigner(m, keys.SigningKey()); err != nil {
154		return err
155	}
156
157	// Cache the paper keys globally now that we're logged in
158	m = m.WithGlobalActiveDevice()
159	m.ActiveDevice().CacheProvisioningKey(m, keys)
160
161	return nil
162}
163
164func (e *PaperProvisionEngine) sendNotification(m libkb.MetaContext) {
165	e.G().NotifyRouter.HandleLogin(m.Ctx(), string(e.G().Env.GetUsername()))
166}
167
168func (e *PaperProvisionEngine) SubConsumers() []libkb.UIConsumer {
169	return []libkb.UIConsumer{
170		&loginLoadUser{},
171	}
172}
173
174func (e *PaperProvisionEngine) Result() error {
175	return e.result
176}
177
178// copied from loginProvision
179func (e *PaperProvisionEngine) fetchLKS(m libkb.MetaContext, encKey libkb.GenericKey) error {
180	gen, clientLKS, err := fetchLKS(m, encKey)
181	if err != nil {
182		return err
183	}
184	e.lks = libkb.NewLKSecWithClientHalf(clientLKS, gen, e.User.GetUID())
185	return nil
186}
187
188// copied from loginProvision
189// makeDeviceKeysWithSigner creates device keys given a signing
190// key.
191func (e *PaperProvisionEngine) makeDeviceKeysWithSigner(m libkb.MetaContext, signer libkb.GenericKey) error {
192	args, err := e.makeDeviceWrapArgs(m)
193	if err != nil {
194		return err
195	}
196	args.Signer = signer
197	args.IsEldest = false // just to be explicit
198	args.EldestKID = e.User.GetEldestKID()
199
200	return e.makeDeviceKeys(m, args)
201}
202
203// copied from loginProvision
204// makeDeviceWrapArgs creates a base set of args for DeviceWrap.
205// It ensures that LKSec is created.  It also gets a new device
206// name for this device.
207func (e *PaperProvisionEngine) makeDeviceWrapArgs(m libkb.MetaContext) (*DeviceWrapArgs, error) {
208	if err := e.ensureLKSec(m); err != nil {
209		return nil, err
210	}
211
212	return &DeviceWrapArgs{
213		Me:             e.User,
214		DeviceName:     e.DeviceName,
215		DeviceType:     keybase1.DeviceTypeV2_DESKTOP,
216		Lks:            e.lks,
217		PerUserKeyring: e.perUserKeyring,
218	}, nil
219}
220
221// Copied from loginProvision. makeDeviceKeys uses DeviceWrap to
222// generate device keys and sets active device.
223func (e *PaperProvisionEngine) makeDeviceKeys(m libkb.MetaContext, args *DeviceWrapArgs) error {
224	e.deviceWrapEng = NewDeviceWrap(m.G(), args)
225	return RunEngine2(m, e.deviceWrapEng)
226}
227
228// copied from loginProvision
229// ensureLKSec ensures we have LKSec for saving device keys.
230func (e *PaperProvisionEngine) ensureLKSec(m libkb.MetaContext) error {
231	if e.lks != nil {
232		return nil
233	}
234
235	pps, err := e.ppStream(m)
236	if err != nil {
237		return err
238	}
239
240	e.lks = libkb.NewLKSec(pps, e.User.GetUID())
241	return nil
242}
243
244// copied from loginProvision
245// ppStream gets the passphrase stream from the cache
246func (e *PaperProvisionEngine) ppStream(m libkb.MetaContext) (*libkb.PassphraseStream, error) {
247	if m.LoginContext() == nil {
248		return nil, errors.New("loginProvision: ppStream() -> nil ctx.LoginContext")
249	}
250	cached := m.LoginContext().PassphraseStreamCache()
251	if cached == nil {
252		return nil, errors.New("loginProvision: ppStream() -> nil PassphraseStreamCache")
253	}
254	return cached.PassphraseStream(), nil
255}
256