1// Copyright 2019 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import "fmt"
7
8type SecretStoreFallbackBehavior int
9
10const (
11	SecretStoreFallbackBehaviorOnError SecretStoreFallbackBehavior = iota
12	SecretStoreFallbackBehaviorAlways
13	SecretStoreFallbackBehaviorNever
14)
15
16type SecretStoreUpgradeable struct {
17	a                              SecretStoreAll
18	labelA                         string
19	b                              SecretStoreAll
20	labelB                         string
21	shouldUpgradeOpportunistically func() bool
22	shouldStoreInFallback          func(*SecretStoreOptions) SecretStoreFallbackBehavior
23	options                        *SecretStoreOptions
24}
25
26var _ SecretStoreAll = (*SecretStoreUpgradeable)(nil)
27
28func NewSecretStoreUpgradeable(a, b SecretStoreAll, labelA, labelB string, shouldUpgradeOpportunistically func() bool, shouldStoreInFallback func(*SecretStoreOptions) SecretStoreFallbackBehavior) *SecretStoreUpgradeable {
29	return &SecretStoreUpgradeable{
30		a:                              a,
31		labelA:                         labelA,
32		b:                              b,
33		labelB:                         labelB,
34		shouldUpgradeOpportunistically: shouldUpgradeOpportunistically,
35		shouldStoreInFallback:          shouldStoreInFallback,
36	}
37}
38
39func (s *SecretStoreUpgradeable) RetrieveSecret(mctx MetaContext, username NormalizedUsername) (secret LKSecFullSecret, err error) {
40	defer mctx.Trace(fmt.Sprintf("SecretStoreUpgradeable.RetrieveSecret(%s)", username),
41		&err)()
42
43	mctx.Debug("Trying to retrieve secret from primary store (%s)", s.labelA)
44	secret, err1 := s.a.RetrieveSecret(mctx, username)
45	if err1 == nil {
46		// Found secret in primary store - return, we don't need to do anything
47		// else here.
48		mctx.Debug("Found secret in primary store (%s)", s.labelA)
49		return secret, nil
50	}
51
52	mctx.Debug("Failed to find secret in primary store (%s): %s, falling back to %s.", s.labelA, err1, s.labelB)
53
54	secret, err2 := s.b.RetrieveSecret(mctx, username)
55	if err2 != nil {
56		mctx.Debug("Failed to retrieve secret from secondary store (%s): %v", s.labelB, err2)
57		// Do not return combined errors here. We want to return typed errors,
58		// like: `SecretStoreError`. Secret store API consumers rely on error
59		// types.
60		return LKSecFullSecret{}, err2
61	}
62
63	shouldUpgrade := s.shouldUpgradeOpportunistically()
64	fallbackBehavior := s.shouldStoreInFallback(s.options)
65	mctx.Debug("Fallback settings are: shouldUpgrade: %t, fallbackBehavior: %v", shouldUpgrade, fallbackBehavior)
66	if !shouldUpgrade || fallbackBehavior == SecretStoreFallbackBehaviorAlways {
67		// Do not upgrade opportunistically, or we are still in Fallback Mode
68		// ALWAYS and should exclusively use store B - do not try fall through
69		// to try to store in A.
70		mctx.Debug("Not trying to upgrade after retrieving from secondary store (%s)", s.labelB)
71		return secret, nil
72	}
73
74	mctx.Debug("Secret found in secondary store (%s), trying to upgrade to primary store (%s)", s.labelB, s.labelA)
75
76	storeAErr := s.a.StoreSecret(mctx, username, secret)
77	if storeAErr == nil {
78		mctx.Debug("Upgraded secret for %s to primary store (%s)", username, s.labelA)
79
80		clearBErr := s.b.ClearSecret(mctx, username)
81		mctx.Debug("After secret upgrade: clearSecret from secondary store (%s) returned: %v", s.labelA, clearBErr)
82	} else {
83		mctx.Debug("Failed to upgrade secret for %s to primary store (%s): %s", username, s.labelA, storeAErr)
84	}
85	return secret, nil
86}
87
88func (s *SecretStoreUpgradeable) StoreSecret(mctx MetaContext, username NormalizedUsername, secret LKSecFullSecret) (err error) {
89	defer mctx.Trace("SecretStoreUpgradeable.StoreSecret", &err)()
90
91	fallbackBehavior := s.shouldStoreInFallback(s.options)
92	if fallbackBehavior == SecretStoreFallbackBehaviorAlways {
93		mctx.Debug("shouldStoreInFallback returned ALWAYS for options %+v, storing in secondary store (%s)", s.options, s.labelB)
94		return s.b.StoreSecret(mctx, username, secret)
95	}
96
97	err1 := s.a.StoreSecret(mctx, username, secret)
98	if err1 == nil {
99		mctx.Debug("Stored secret for %s in primary store (%s), attempting clear for secondary store (%s)", username, s.labelA, s.labelB)
100		clearBErr := s.b.ClearSecret(mctx, username)
101		if clearBErr == nil {
102			// Store may also return nil error when there was nothing to clear.
103			mctx.Debug("ClearSecret error=<nil> for %s from secondary store (%s)", username, s.labelB)
104		} else {
105			mctx.Debug("Failed to clear secret for %s from secondary store (%s): %s", username, s.labelB, clearBErr)
106		}
107		return nil
108	}
109
110	if fallbackBehavior == SecretStoreFallbackBehaviorNever {
111		mctx.Warning("Failed to reach system keyring (primary store (%s): %s), not falling back to secondary store (%s) because of fallback behavior.", s.labelA, err1, s.labelB)
112		return err1
113	}
114
115	mctx.Warning("Failed to reach system keyring (primary store (%s): %s), falling back to secondary store (%s).", s.labelA, err1, s.labelB)
116	err2 := s.b.StoreSecret(mctx, username, secret)
117	if err2 == nil {
118		return nil
119	}
120	err = CombineErrors(err1, err2)
121	return err
122}
123
124func (s *SecretStoreUpgradeable) ClearSecret(mctx MetaContext, username NormalizedUsername) (err error) {
125	defer mctx.Trace("SecretStoreUpgradeable.ClearSecret", &err)()
126	err1 := s.a.ClearSecret(mctx, username)
127	if err1 != nil {
128		mctx.Debug("Failed to clear secret in primary store (%s): %s", s.labelA, err1)
129	}
130	err2 := s.b.ClearSecret(mctx, username)
131	if err2 != nil {
132		mctx.Debug("Failed to clear secret in secondary store (%s): %s", s.labelB, err2)
133	}
134	// Only return an error if both failed
135	if err1 != nil && err2 != nil {
136		err := CombineErrors(err1, err2)
137		return err
138	}
139	return nil
140}
141
142func (s *SecretStoreUpgradeable) GetUsersWithStoredSecrets(mctx MetaContext) (usernames []string, err error) {
143	defer mctx.Trace("SecretStoreUpgradeable.GetUsersWithStoredSecrets", &err)()
144	usernameMap := make(map[string]bool)
145	usernamesA, err1 := s.a.GetUsersWithStoredSecrets(mctx)
146	if err1 == nil {
147		for _, u := range usernamesA {
148			usernameMap[u] = true
149		}
150	} else {
151		mctx.Debug("Failed to GetUsersWithStoredSecrets in primary store (%s): %s", s.labelA, err1)
152	}
153	usernamesB, err2 := s.b.GetUsersWithStoredSecrets(mctx)
154	if err2 == nil {
155		for _, u := range usernamesB {
156			usernameMap[u] = true
157		}
158	} else {
159		mctx.Debug("Failed to GetUsersWithStoredSecrets in secondary store (%s): %s", s.labelB, err2)
160	}
161
162	for username := range usernameMap {
163		usernames = append(usernames, username)
164	}
165
166	err = CombineErrors(err1, err2)
167	// Only return an error if both failed
168	if err1 != nil && err2 != nil {
169		return nil, err
170	}
171	return usernames, nil
172}
173
174func (s *SecretStoreUpgradeable) GetOptions(MetaContext) *SecretStoreOptions { return s.options }
175func (s *SecretStoreUpgradeable) SetOptions(_ MetaContext, options *SecretStoreOptions) {
176	s.options = options
177}
178