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	"fmt"
9	"sort"
10	"strings"
11	"sync"
12	"time"
13
14	keybase1 "github.com/keybase/client/go/protocol/keybase1"
15)
16
17type SecretRetriever interface {
18	RetrieveSecret(m MetaContext) (LKSecFullSecret, error)
19}
20
21type SecretStoreOptions struct {
22	RandomPw bool
23}
24
25func DefaultSecretStoreOptions() SecretStoreOptions {
26	return SecretStoreOptions{}
27}
28
29type SecretStorer interface {
30	StoreSecret(m MetaContext, secret LKSecFullSecret) error
31}
32
33// SecretStore stores/retreives the keyring-resident secrets for a given user.
34type SecretStore interface {
35	SecretRetriever
36	SecretStorer
37	GetOptions(mctx MetaContext) *SecretStoreOptions
38	SetOptions(mctx MetaContext, options *SecretStoreOptions)
39}
40
41// SecretStoreall stores/retreives the keyring-resider secrets for **all** users
42// on this system.
43type SecretStoreAll interface {
44	RetrieveSecret(mctx MetaContext, username NormalizedUsername) (LKSecFullSecret, error)
45	StoreSecret(mctx MetaContext, username NormalizedUsername, secret LKSecFullSecret) error
46	GetOptions(mctx MetaContext) *SecretStoreOptions
47	SetOptions(mctx MetaContext, options *SecretStoreOptions)
48	ClearSecret(mctx MetaContext, username NormalizedUsername) error
49	GetUsersWithStoredSecrets(mctx MetaContext) ([]string, error)
50}
51
52// SecretStoreImp is a specialization of a SecretStoreAll for just one username.
53// You specify that username at the time on construction and then it doesn't change.
54type SecretStoreImp struct {
55	username NormalizedUsername
56	store    *SecretStoreLocked
57	secret   LKSecFullSecret
58	sync.Mutex
59}
60
61var _ SecretStore = (*SecretStoreImp)(nil)
62
63func (s *SecretStoreImp) RetrieveSecret(m MetaContext) (LKSecFullSecret, error) {
64	s.Lock()
65	defer s.Unlock()
66
67	if !s.secret.IsNil() {
68		return s.secret, nil
69	}
70	sec, err := s.store.RetrieveSecret(m, s.username)
71	if err != nil {
72		return sec, err
73	}
74	s.secret = sec
75	return sec, nil
76}
77
78func (s *SecretStoreImp) StoreSecret(m MetaContext, secret LKSecFullSecret) error {
79	s.Lock()
80	defer s.Unlock()
81
82	// clear out any in-memory secret in this instance
83	s.secret = LKSecFullSecret{}
84	return s.store.StoreSecret(m, s.username, secret)
85}
86
87func (s *SecretStoreImp) GetOptions(mctx MetaContext) *SecretStoreOptions {
88	if s.store != nil {
89		return s.store.GetOptions(mctx)
90	}
91	return nil
92
93}
94func (s *SecretStoreImp) SetOptions(mctx MetaContext, options *SecretStoreOptions) {
95	if s.store != nil {
96		s.store.SetOptions(mctx, options)
97	}
98}
99
100// NewSecretStore returns a SecretStore interface that is only used for
101// a short period of time (i.e. one function block).  Multiple calls to RetrieveSecret()
102// will only call the underlying store.RetrieveSecret once.
103func NewSecretStore(m MetaContext, username NormalizedUsername) SecretStore {
104	store := m.G().SecretStore()
105	if store != nil {
106		m.Debug("NewSecretStore: reifying SecretStoreImp for %q", username)
107		return &SecretStoreImp{
108			username: username,
109			store:    store,
110		}
111	}
112	return nil
113}
114
115func GetConfiguredAccountsFromProvisionedUsernames(m MetaContext, s SecretStoreAll, currentUsername NormalizedUsername, allUsernames []NormalizedUsername) ([]keybase1.ConfiguredAccount, error) {
116	if !currentUsername.IsNil() {
117		allUsernames = append(allUsernames, currentUsername)
118	}
119
120	accounts := make(map[NormalizedUsername]keybase1.ConfiguredAccount)
121	for _, username := range allUsernames {
122		accounts[username] = keybase1.ConfiguredAccount{
123			Username:  username.String(),
124			IsCurrent: username.Eq(currentUsername),
125		}
126	}
127
128	// Get the full names
129	uids := make([]keybase1.UID, len(allUsernames))
130	for idx, username := range allUsernames {
131		uids[idx] = GetUIDByNormalizedUsername(m.G(), username)
132	}
133	usernamePackages, err := m.G().UIDMapper.MapUIDsToUsernamePackages(m.Ctx(), m.G(),
134		uids, time.Hour*24, time.Second*10, false)
135	if err != nil {
136		if usernamePackages != nil {
137			// If data is returned, interpret the error as a warning
138			m.G().Log.CInfof(m.Ctx(),
139				"error while retrieving full names: %+v", err)
140		} else {
141			return nil, err
142		}
143	}
144	for _, uPackage := range usernamePackages {
145		if uPackage.FullName == nil {
146			continue
147		}
148		if account, ok := accounts[uPackage.NormalizedUsername]; ok {
149			account.Fullname = uPackage.FullName.FullName
150			accounts[uPackage.NormalizedUsername] = account
151		}
152	}
153
154	// Check for secrets
155
156	var storedSecretUsernames []string
157	if s != nil {
158		storedSecretUsernames, err = s.GetUsersWithStoredSecrets(m)
159	}
160	if err != nil {
161		return nil, err
162	}
163
164	for _, username := range storedSecretUsernames {
165		nu := NewNormalizedUsername(username)
166		account, ok := accounts[nu]
167		if ok {
168			account.HasStoredSecret = true
169			accounts[nu] = account
170		}
171	}
172
173	configuredAccounts := make([]keybase1.ConfiguredAccount, 0, len(accounts))
174	for _, account := range accounts {
175		configuredAccounts = append(configuredAccounts, account)
176	}
177
178	loginTimes, err := getLoginTimes(m)
179	if err != nil {
180		m.Warning("Failed to get login times: %s", err)
181		loginTimes = make(loginTimeMap)
182	}
183
184	sort.Slice(configuredAccounts, func(i, j int) bool {
185		iUsername := configuredAccounts[i].Username
186		jUsername := configuredAccounts[j].Username
187		iTime, iOk := loginTimes[NormalizedUsername(iUsername)]
188		jTime, jOk := loginTimes[NormalizedUsername(jUsername)]
189		if !iOk && !jOk {
190			iSignedIn := configuredAccounts[i].HasStoredSecret
191			jSignedIn := configuredAccounts[j].HasStoredSecret
192			if iSignedIn != jSignedIn {
193				return iSignedIn
194			}
195			return strings.Compare(iUsername, jUsername) < 0
196		}
197		if !iOk {
198			return false
199		}
200		if !jOk {
201			return true
202		}
203		return iTime.After(jTime)
204	})
205
206	return configuredAccounts, nil
207}
208
209func GetConfiguredAccounts(m MetaContext, s SecretStoreAll) ([]keybase1.ConfiguredAccount, error) {
210	currentUsername, allUsernames, err := GetAllProvisionedUsernames(m)
211	if err != nil {
212		return nil, err
213	}
214	return GetConfiguredAccountsFromProvisionedUsernames(m, s, currentUsername, allUsernames)
215}
216
217func ClearStoredSecret(m MetaContext, username NormalizedUsername) error {
218	ss := m.G().SecretStore()
219	if ss == nil {
220		return nil
221	}
222	return ss.ClearSecret(m, username)
223}
224
225// SecretStoreLocked protects a SecretStoreAll with a mutex. It wraps two different
226// SecretStoreAlls: one in memory and one in disk. In all cases, we always have a memory
227// backing. If the OS and options provide one, we can additionally have a disk-backed
228// secret store. It's a write-through cache, so on RetrieveSecret, the memory store
229// will be checked first, and then the disk store.
230type SecretStoreLocked struct {
231	sync.Mutex
232	mem  SecretStoreAll
233	disk SecretStoreAll
234}
235
236func NewSecretStoreLocked(m MetaContext) *SecretStoreLocked {
237	// We always make an on-disk secret store, but if the user has opted not
238	// to remember their passphrase, we don't store it on-disk.
239	return &SecretStoreLocked{
240		mem:  NewSecretStoreMem(),
241		disk: NewSecretStoreAll(m),
242	}
243}
244
245func (s *SecretStoreLocked) isNil() bool {
246	return s.mem == nil && s.disk == nil
247}
248
249func (s *SecretStoreLocked) ClearMem() {
250	s.mem = NewSecretStoreMem()
251}
252
253func (s *SecretStoreLocked) RetrieveSecret(m MetaContext, username NormalizedUsername) (LKSecFullSecret, error) {
254	if s == nil || s.isNil() {
255		return LKSecFullSecret{}, nil
256	}
257	s.Lock()
258	defer s.Unlock()
259
260	res, err := s.mem.RetrieveSecret(m, username)
261	if !res.IsNil() && err == nil {
262		return res, nil
263	}
264	if err != nil {
265		m.Debug("SecretStoreLocked#RetrieveSecret: memory fetch error: %s", err.Error())
266	}
267	if s.disk == nil {
268		return res, err
269	}
270
271	res, err = s.disk.RetrieveSecret(m, username)
272	if err != nil {
273		return res, err
274	}
275	tmp := s.mem.StoreSecret(m, username, res)
276	if tmp != nil {
277		m.Debug("SecretStoreLocked#RetrieveSecret: failed to store secret in memory: %s", tmp.Error())
278	}
279	return res, err
280}
281
282func (s *SecretStoreLocked) StoreSecret(m MetaContext, username NormalizedUsername, secret LKSecFullSecret) error {
283	if s == nil || s.isNil() {
284		return nil
285	}
286	s.Lock()
287	defer s.Unlock()
288	err := s.mem.StoreSecret(m, username, secret)
289	if err != nil {
290		m.Debug("SecretStoreLocked#StoreSecret: failed to store secret in memory: %s", err.Error())
291	}
292	if s.disk == nil {
293		return err
294	}
295	if !m.G().Env.GetRememberPassphrase(username) {
296		m.Debug("SecretStoreLocked: should not remember passphrase for %s; not storing on disk", username)
297		return err
298	}
299	return s.disk.StoreSecret(m, username, secret)
300}
301
302func (s *SecretStoreLocked) ClearSecret(m MetaContext, username NormalizedUsername) error {
303
304	if username.IsNil() {
305		m.Debug("NOOPing SecretStoreLocked#ClearSecret for empty username")
306		return nil
307	}
308
309	if s == nil || s.isNil() {
310		return nil
311	}
312	s.Lock()
313	defer s.Unlock()
314
315	err := s.mem.ClearSecret(m, username)
316	if err != nil {
317		m.Debug("SecretStoreLocked#ClearSecret: failed to clear memory: %s", err.Error())
318	}
319	if s.disk == nil {
320		return err
321	}
322	return s.disk.ClearSecret(m, username)
323}
324
325func (s *SecretStoreLocked) GetUsersWithStoredSecrets(m MetaContext) ([]string, error) {
326	if s == nil || s.isNil() {
327		return nil, nil
328	}
329	s.Lock()
330	defer s.Unlock()
331	users := make(map[string]struct{})
332
333	memUsers, memErr := s.mem.GetUsersWithStoredSecrets(m)
334	if memErr == nil {
335		for _, memUser := range memUsers {
336			users[memUser] = struct{}{}
337		}
338	}
339	if s.disk == nil {
340		return memUsers, memErr
341	}
342	diskUsers, diskErr := s.disk.GetUsersWithStoredSecrets(m)
343	if diskErr == nil {
344		for _, diskUser := range diskUsers {
345			users[diskUser] = struct{}{}
346		}
347	}
348	if memErr != nil && diskErr != nil {
349		return nil, CombineErrors(memErr, diskErr)
350	}
351	var ret []string
352	for user := range users {
353		ret = append(ret, user)
354	}
355	return ret, nil
356}
357
358func (s *SecretStoreLocked) PrimeSecretStores(mctx MetaContext) (err error) {
359	if mctx.G().Env.GetSecretStorePrimingDisabled() {
360		mctx.Debug("Skipping PrimeSecretStores, disabled in env")
361		return nil
362	}
363	if s == nil || s.isNil() {
364		return errors.New("secret store is not available")
365	}
366	if s.disk != nil {
367		err = PrimeSecretStore(mctx, s.disk)
368		if err != nil {
369			return err
370		}
371	}
372	err = PrimeSecretStore(mctx, s.mem)
373	return err
374}
375
376func (s *SecretStoreLocked) IsPersistent() bool {
377	if s == nil || s.isNil() {
378		return false
379	}
380	return s.disk != nil
381}
382
383func (s *SecretStoreLocked) GetOptions(mctx MetaContext) *SecretStoreOptions {
384	if s.disk != nil {
385		return s.disk.GetOptions(mctx)
386	}
387	return nil
388}
389func (s *SecretStoreLocked) SetOptions(mctx MetaContext, options *SecretStoreOptions) {
390	if s.disk != nil {
391		s.disk.SetOptions(mctx, options)
392	}
393}
394
395// PrimeSecretStore runs a test with current platform's secret store, trying to
396// store, retrieve, and then delete a secret with an arbitrary name. This should
397// be done before provisioning or logging in
398func PrimeSecretStore(mctx MetaContext, ss SecretStoreAll) (err error) {
399	defer func() {
400		if err != nil {
401			go reportPrimeSecretStoreFailure(mctx.BackgroundWithLogTags(), ss, err)
402		}
403	}()
404	defer mctx.Trace("PrimeSecretStore", &err)()
405
406	// Generate test username and test secret
407	testUsername, err := RandString("test_ss_", 5)
408	// RandString returns base32 encoded random bytes, make it look like a
409	// Keybase username. This is not required, though.
410	testUsername = strings.ToLower(strings.Replace(testUsername, "=", "", -1))
411	if err != nil {
412		return err
413	}
414	randBytes, err := RandBytes(LKSecLen)
415	if err != nil {
416		return err
417	}
418	mctx.Debug("PrimeSecretStore: priming secret store with username %q and secret %v", testUsername, randBytes)
419	testNormUsername := NormalizedUsername(testUsername)
420	var secretF [LKSecLen]byte
421	copy(secretF[:], randBytes)
422	testSecret := LKSecFullSecret{f: &secretF}
423
424	defer func() {
425		err2 := ss.ClearSecret(mctx, testNormUsername)
426		mctx.Debug("PrimeSecretStore: clearing test secret store entry")
427		if err2 != nil {
428			mctx.Debug("PrimeSecretStore: clearing secret store entry returned an error: %s", err2)
429			if err == nil {
430				err = err2
431			} else {
432				mctx.Debug("suppressing store clearing error because something else has errored prior")
433			}
434		}
435	}()
436
437	// Try to fetch first, we should get an error back.
438	_, err = ss.RetrieveSecret(mctx, testNormUsername)
439	if err == nil {
440		return errors.New("managed to retrieve secret before storing it")
441	} else if err != nil {
442		mctx.Debug("PrimeSecretStore: error when retrieving secret that wasn't stored yet: %q, as expected", err)
443	}
444
445	// Put secret in secret store through `SecretStore` interface.
446	err = ss.StoreSecret(mctx, testNormUsername, testSecret)
447	if err != nil {
448		return fmt.Errorf("error while storing secret: %s", err)
449	}
450
451	// Recreate test store with same username, try to retrieve secret.
452	retrSecret, err := ss.RetrieveSecret(mctx, testNormUsername)
453	if err != nil {
454		return fmt.Errorf("error while retrieving secret: %s", err)
455	}
456	mctx.Debug("PrimeSecretStore: retrieved secret: %v", retrSecret.f)
457	if !retrSecret.Equal(testSecret) {
458		return errors.New("managed to retrieve test secret but it didn't match the stored one")
459	}
460
461	mctx.Debug("PrimeSecretStore: retrieved secret matched!")
462	return nil
463}
464
465func reportPrimeSecretStoreFailure(mctx MetaContext, ss SecretStoreAll, reportErr error) {
466	var err error
467	defer mctx.Trace("reportPrimeSecretStoreFailure", &err)()
468	osVersion, osBuild, err := OSVersionAndBuild()
469	if err != nil {
470		mctx.Debug("os info error: %v", err)
471	}
472	apiArg := APIArg{
473		Endpoint:    "device/error",
474		SessionType: APISessionTypeNONE,
475		Args: HTTPArgs{
476			"event":      S{Val: "prime_secret_store"},
477			"msg":        S{Val: fmt.Sprintf("[%T] [%T] %v", ss, reportErr, reportErr.Error())},
478			"run_mode":   S{Val: string(mctx.G().GetRunMode())},
479			"kb_version": S{Val: VersionString()},
480			"os_version": S{Val: osVersion},
481			"os_build":   S{Val: osBuild},
482		},
483		RetryCount:     3,
484		InitialTimeout: 10 * time.Second,
485	}
486	var apiRes AppStatusEmbed
487	err = mctx.G().API.PostDecode(mctx, apiArg, &apiRes)
488}
489
490type loginTimeMap map[NormalizedUsername]time.Time
491
492func loginTimesDbKey(mctx MetaContext) DbKey {
493	return DbKey{
494		// Should not be per-user, so not using uid in the db key
495		Typ: DBLoginTimes,
496		Key: "",
497	}
498}
499
500func getLoginTimes(mctx MetaContext) (ret loginTimeMap, err error) {
501	found, err := mctx.G().LocalDb.GetInto(&ret, loginTimesDbKey(mctx))
502	if err != nil {
503		return ret, err
504	}
505	if !found {
506		ret = make(loginTimeMap)
507	}
508	return ret, nil
509}
510
511func RecordLoginTime(mctx MetaContext, username NormalizedUsername) (err error) {
512	ret, err := getLoginTimes(mctx)
513	if err != nil {
514		mctx.Warning("failed to get login times from db; overwriting existing data: %s", err)
515		ret = make(loginTimeMap)
516	}
517	ret[username] = time.Now()
518	return mctx.G().LocalDb.PutObj(loginTimesDbKey(mctx), nil, ret)
519}
520