1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"errors"
8	"time"
9)
10
11type bug3964Repairman struct {
12	Contextified
13}
14
15func newBug3964Repairman(g *GlobalContext) *bug3964Repairman {
16	return &bug3964Repairman{Contextified: NewContextified(g)}
17}
18
19func (b *bug3964Repairman) attemptRepair(m MetaContext, lksec *LKSec, dkm DeviceKeyMap) (ran bool, serverHalfSet *LKSecServerHalfSet, err error) {
20	defer m.Trace("bug3964Repairman#attemptRepair", &err)()
21	var oldKeyring, newKeyring *SKBKeyringFile
22	lctx := m.LoginContext()
23	oldKeyring, err = lctx.Keyring(m)
24	if err != nil {
25		return false, nil, err
26	}
27	newKeyring, serverHalfSet, err = oldKeyring.Bug3964Repair(m, lksec, dkm)
28	if err != nil {
29		return false, nil, err
30	}
31	if newKeyring == nil {
32		return false, nil, nil
33	}
34	if err = newKeyring.Save(); err != nil {
35		m.Debug("Error saving new keyring: %s", err)
36		return false, nil, err
37	}
38	lctx.ClearKeyring()
39	return true, serverHalfSet, err
40}
41
42func (b *bug3964Repairman) loadLKSecServerDetails(m MetaContext, lksec *LKSec) (ret DeviceKeyMap, err error) {
43	defer m.Trace("bug3964Repairman#loadLKSecServerDetails", &err)()
44	ret, err = lksec.LoadServerDetails(m)
45	if err != nil {
46		return nil, err
47	}
48	lksec.SetFullSecret(m)
49	return ret, err
50}
51
52func (b *bug3964Repairman) updateSecretStore(m MetaContext, nun NormalizedUsername, lksec *LKSec) error {
53	fs := lksec.FullSecret()
54	ss := b.G().SecretStore()
55	if fs.IsNil() {
56		m.Warning("Got unexpected nil full secret")
57		return ss.ClearSecret(m, nun)
58	}
59	return ss.StoreSecret(m, nun, fs)
60}
61
62func (b *bug3964Repairman) saveRepairmanVisit(nun NormalizedUsername) (err error) {
63	defer b.G().Trace("bug3964Repairman#saveRepairmanVisit", &err)()
64	return b.G().Env.GetConfigWriter().SetBug3964RepairTime(nun, time.Now())
65}
66
67func (b *bug3964Repairman) postToServer(m MetaContext, serverHalfSet *LKSecServerHalfSet, ppgen PassphraseGeneration, nun NormalizedUsername) (err error) {
68	defer m.G().CTrace(m.Ctx(), "bug3964Repairman#postToServer", &err)()
69	if serverHalfSet == nil {
70		return errors.New("internal error --- had nil server half set")
71	}
72	_, err = m.G().API.Post(m, APIArg{
73		Endpoint:    "user/bug_3964_repair",
74		SessionType: APISessionTypeREQUIRED,
75		Args: HTTPArgs{
76			"device_id":         S{Val: m.G().Env.GetDeviceIDForUsername(nun).String()},
77			"ppgen":             I{Val: int(ppgen)},
78			"lks_server_halves": S{Val: serverHalfSet.EncodeToHexList()},
79		},
80	})
81	return err
82}
83
84func (b *bug3964Repairman) computeShortCircuit(nun NormalizedUsername) (ss bool, err error) {
85	defer b.G().Trace("bug3964Repairman#computeShortCircuit", &err)()
86	repairTime, tmpErr := b.G().Env.GetConfig().GetBug3964RepairTime(nun)
87
88	// Ignore any decoding errors
89	if tmpErr != nil {
90		b.G().Log.Warning("Problem reading previous bug 3964 repair time: %s", tmpErr)
91	}
92
93	if repairTime.IsZero() {
94		b.G().Log.Debug("| repair time is zero or wasn't set")
95		return false, nil
96	}
97	var fileTime time.Time
98	fileTime, err = StatSKBKeyringMTime(nun, b.G())
99	if err != nil {
100		return false, err
101	}
102	ss = !repairTime.Before(fileTime)
103	b.G().Log.Debug("| Checking repair-time (%s) v file-write-time (%s): shortCircuit=%v", repairTime, fileTime, ss)
104	return ss, nil
105}
106
107func (b *bug3964Repairman) fixLKSClientHalf(m MetaContext, lksec *LKSec, ppgen PassphraseGeneration) (err error) {
108	defer m.Trace("bug3964Repairman#fixLKSClientHalf", &err)()
109	var me *User
110	var encKey GenericKey
111	var ctext string
112
113	me, err = LoadMe(NewLoadUserArgWithMetaContext(m))
114	if err != nil {
115		return err
116	}
117	encKey, err = me.GetDeviceSubkey()
118	if err != nil {
119		return err
120	}
121	// make client half recovery
122	kid := encKey.GetKID()
123	ctext, err = lksec.EncryptClientHalfRecovery(encKey)
124	if err != nil {
125		return err
126	}
127
128	_, err = b.G().API.Post(m, APIArg{
129		Endpoint:    "device/update_lks_client_half",
130		SessionType: APISessionTypeREQUIRED,
131		Args: HTTPArgs{
132			"ppgen":           I{Val: int(ppgen)},
133			"kid":             S{Val: kid.String()},
134			"lks_client_half": S{Val: ctext},
135		},
136	})
137
138	return err
139}
140
141// Run the engine
142func (b *bug3964Repairman) Run(m MetaContext) (err error) {
143	defer m.G().CTrace(m.Ctx(), "bug3964Repairman#Run", &err)()
144	lctx := m.LoginContext()
145	pps := lctx.PassphraseStreamCache().PassphraseStream()
146
147	var lksec *LKSec
148	var ran bool
149	var dkm DeviceKeyMap
150	var ss bool
151	var serverHalfSet *LKSecServerHalfSet
152	nun := m.G().Env.GetUsername()
153
154	if m.G().TestOptions.NoBug3964Repair {
155		m.G().Log.CDebugf(m.Ctx(), "| short circuit due to test options")
156		return nil
157	}
158
159	if pps == nil {
160		m.G().Log.CDebugf(m.Ctx(), "| Can't run repairman without a passphrase stream")
161		return nil
162	}
163
164	if ss, err = b.computeShortCircuit(nun); err != nil {
165		return err
166	}
167
168	if ss {
169		// This logline is asserted in testing in bug_3964_repairman_test
170		m.G().Log.CDebugf(m.Ctx(), "| Repairman already visited after file update; bailing out")
171		return nil
172	}
173
174	// This logline is asserted in testing in bug_3964_repairman_test
175	m.G().Log.CDebugf(m.Ctx(), "| Repairman wasn't short-circuited")
176
177	lksec, err = pps.ToLKSec(lctx.GetUID())
178	if err != nil {
179		return err
180	}
181
182	if dkm, err = b.loadLKSecServerDetails(m, lksec); err != nil {
183		return err
184	}
185
186	if ran, serverHalfSet, err = b.attemptRepair(m, lksec, dkm); err != nil {
187		return err
188	}
189
190	if err != nil {
191		return err
192	}
193
194	m.G().Log.CDebugf(m.Ctx(), "| SKB keyring repair completed; edits=%v", ran)
195
196	if !ran {
197		return b.saveRepairmanVisit(nun)
198	}
199
200	if err := b.fixLKSClientHalf(m, lksec, pps.Generation()); err != nil {
201		return err
202	}
203
204	if ussErr := b.updateSecretStore(m, nun, lksec); ussErr != nil {
205		m.G().Log.CWarningf(m.Ctx(), "Error in secret store manipulation: %s", ussErr)
206	} else {
207		err := b.saveRepairmanVisit(nun)
208		if err != nil {
209			return err
210		}
211	}
212
213	err = b.postToServer(m, serverHalfSet, pps.Generation(), nun)
214
215	return err
216}
217
218func RunBug3964Repairman(m MetaContext) error {
219	err := newBug3964Repairman(m.G()).Run(m)
220	if err != nil {
221		m.G().Log.CDebugf(m.Ctx(), "Error running Bug 3964 repairman: %s", err)
222	}
223	return err
224}
225