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	"bytes"
8	"io/ioutil"
9	"os"
10	"path/filepath"
11	"runtime"
12	"sort"
13	"testing"
14
15	"github.com/stretchr/testify/require"
16)
17
18func newNilMetaContext() MetaContext {
19	return NewMetaContextTODO(nil)
20}
21
22func testSSDir(t *testing.T) (string, func()) {
23	td, err := ioutil.TempDir("", "ss")
24	require.NoError(t, err)
25
26	create := func(name, secret string) {
27		err := ioutil.WriteFile(filepath.Join(td, name+".ss"), []byte(secret), PermFile)
28		require.NoError(t, err)
29	}
30
31	// create some ss files
32	create("alice", "alicealicealicealicealicealiceal")
33	create("bob", "bobbobbobbobbobbobbobbobbobbobbo")
34
35	cleanup := func() {
36		if err := os.RemoveAll(td); err != nil {
37			t.Log(err)
38		}
39	}
40
41	return td, cleanup
42}
43
44func TestSecretStoreFileRetrieveSecret(t *testing.T) {
45	tc := SetupTest(t, "SecretStoreFile", 1)
46	defer tc.Cleanup()
47	m := NewMetaContextForTest(tc)
48
49	td, tdClean := testSSDir(t)
50	defer tdClean()
51
52	cases := map[string]struct {
53		username NormalizedUsername
54		secret   []byte
55		err      error
56	}{
57		"alice":     {"alice", []byte("alicealicealicealicealicealiceal"), nil},
58		"bob":       {"bob", []byte("bobbobbobbobbobbobbobbobbobbobbo"), nil},
59		"not found": {"nobody", nil, NewErrSecretForUserNotFound("nobody")},
60	}
61
62	ss := NewSecretStoreFile(td)
63
64	for _, test := range cases {
65		secret, err := ss.RetrieveSecret(m, test.username)
66		require.Equal(t, test.err, err)
67		require.True(t, bytes.Equal(secret.Bytes(), test.secret))
68	}
69}
70
71func TestSecretStoreFileStoreSecret(t *testing.T) {
72	tc := SetupTest(t, "SecretStoreFile", 1)
73	defer tc.Cleanup()
74	m := NewMetaContextForTest(tc)
75
76	td, tdClean := testSSDir(t)
77	defer tdClean()
78
79	cases := map[string]struct {
80		username NormalizedUsername
81		secret   []byte
82	}{
83		"new entry": {"charlie", []byte("charliecharliecharliecharliechar")},
84		"replace":   {"alice", []byte("alice_next_secret_alice_next_sec")},
85	}
86
87	ss := NewSecretStoreFile(td)
88
89	for _, test := range cases {
90		fs, err := newLKSecFullSecretFromBytes(test.secret)
91		require.NoError(t, err)
92		err = ss.StoreSecret(m, test.username, fs)
93		require.NoError(t, err)
94
95		secret, err := ss.RetrieveSecret(m, test.username)
96		require.NoError(t, err)
97		require.True(t, bytes.Equal(secret.Bytes(), test.secret))
98	}
99}
100
101func TestSecretStoreFileClearSecret(t *testing.T) {
102	tc := SetupTest(t, "SecretStoreFile", 1)
103	defer tc.Cleanup()
104	m := NewMetaContextForTest(tc)
105
106	td, tdClean := testSSDir(t)
107	defer tdClean()
108
109	ss := NewSecretStoreFile(td)
110
111	err := ss.ClearSecret(m, "alice")
112	require.NoError(t, err)
113
114	secret, err := ss.RetrieveSecret(m, "alice")
115	require.IsType(t, SecretStoreError{}, err)
116	require.True(t, secret.IsNil())
117}
118
119func TestSecretStoreFileGetUsersWithStoredSecrets(t *testing.T) {
120	tc := SetupTest(t, "SecretStoreFile", 1)
121	defer tc.Cleanup()
122	m := NewMetaContextForTest(tc)
123
124	td, tdClean := testSSDir(t)
125	defer tdClean()
126
127	ss := NewSecretStoreFile(td)
128
129	users, err := ss.GetUsersWithStoredSecrets(m)
130	require.NoError(t, err)
131	require.Len(t, users, 2)
132	sort.Strings(users)
133	require.Equal(t, users[0], "alice")
134	require.Equal(t, users[1], "bob")
135
136	fs, err := newLKSecFullSecretFromBytes([]byte("xavierxavierxavierxavierxavierxa"))
137	require.NoError(t, err)
138
139	err = ss.StoreSecret(m, "xavier", fs)
140	require.NoError(t, err)
141
142	users, err = ss.GetUsersWithStoredSecrets(m)
143	require.NoError(t, err)
144	require.Len(t, users, 3)
145
146	sort.Strings(users)
147	require.Equal(t, users[0], "alice")
148	require.Equal(t, users[1], "bob")
149	require.Equal(t, users[2], "xavier")
150
151	err = ss.ClearSecret(m, "bob")
152	require.NoError(t, err)
153
154	users, err = ss.GetUsersWithStoredSecrets(m)
155	require.NoError(t, err)
156	require.Len(t, users, 2)
157
158	sort.Strings(users)
159	require.Equal(t, users[0], "alice")
160	require.Equal(t, users[1], "xavier")
161}
162
163func assertExists(t *testing.T, path string) {
164	exists, err := FileExists(path)
165	require.NoError(t, err)
166	require.True(t, exists)
167}
168
169func assertNotExists(t *testing.T, path string) {
170	exists, err := FileExists(path)
171	require.NoError(t, err)
172	require.False(t, exists)
173}
174
175func TestSecretStoreFileRetrieveUpgrade(t *testing.T) {
176	tc := SetupTest(t, "SecretStoreFile", 1)
177	defer tc.Cleanup()
178	m := NewMetaContextForTest(tc)
179
180	td, tdClean := testSSDir(t)
181	defer tdClean()
182
183	assertExists(t, filepath.Join(td, "alice.ss"))
184	assertNotExists(t, filepath.Join(td, "alice.ss2"))
185	assertNotExists(t, filepath.Join(td, "alice.ns2"))
186	assertExists(t, filepath.Join(td, "bob.ss"))
187	assertNotExists(t, filepath.Join(td, "bob.ss2"))
188	assertNotExists(t, filepath.Join(td, "bob.ns2"))
189
190	ss := NewSecretStoreFile(td)
191
192	// retrieve secret for alice should upgrade from alice.ss to alice.ss2
193	secret, err := ss.RetrieveSecret(m, "alice")
194	require.NoError(t, err)
195
196	assertNotExists(t, filepath.Join(td, "alice.ss"))
197	assertExists(t, filepath.Join(td, "alice.ss2"))
198	assertExists(t, filepath.Join(td, "alice.ns2"))
199
200	secretUpgraded, err := ss.RetrieveSecret(m, "alice")
201	require.NoError(t, err)
202	require.True(t, bytes.Equal(secret.Bytes(), secretUpgraded.Bytes()))
203
204	// bob v1 should be untouched
205	assertExists(t, filepath.Join(td, "bob.ss"))
206	assertNotExists(t, filepath.Join(td, "bob.ss2"))
207	assertNotExists(t, filepath.Join(td, "bob.ns2"))
208}
209
210func TestSecretStoreFileNoise(t *testing.T) {
211	tc := SetupTest(t, "SecretStoreFile", 1)
212	defer tc.Cleanup()
213	m := NewMetaContextForTest(tc)
214
215	td, tdClean := testSSDir(t)
216	defer tdClean()
217
218	secret, err := RandBytes(32)
219	require.NoError(t, err)
220
221	lksec, err := newLKSecFullSecretFromBytes(secret)
222	require.NoError(t, err)
223
224	ss := NewSecretStoreFile(td)
225	err = ss.StoreSecret(m, "ogden", lksec)
226	require.NoError(t, err)
227	noise, err := ioutil.ReadFile(filepath.Join(td, "ogden.ns2"))
228	require.NoError(t, err)
229
230	// flip one bit
231	noise[0] ^= 0x01
232
233	err = ioutil.WriteFile(filepath.Join(td, "ogden.ns2"), noise, PermFile)
234	require.NoError(t, err)
235
236	corrupt, err := ss.RetrieveSecret(m, "ogden")
237	require.NoError(t, err)
238
239	require.False(t, bytes.Equal(lksec.Bytes(), corrupt.Bytes()))
240}
241
242func TestPrimeSecretStoreFile(t *testing.T) {
243	td, tdClean := testSSDir(t)
244	defer tdClean()
245
246	tc := SetupTest(t, "secret_store_file", 1)
247	defer tc.Cleanup()
248	tc.G.Env.Test.SecretStorePrimingDisabled = false
249
250	mctx := NewMetaContextForTest(tc)
251	secretStore := NewSecretStoreFile(td)
252	err := PrimeSecretStore(mctx, secretStore)
253	require.NoError(t, err)
254}
255
256func TestPrimeSecretStoreFileFail(t *testing.T) {
257	if runtime.GOOS == "windows" {
258		t.Skip("this test uses chmod, skipping on Windows")
259	}
260
261	tc := SetupTest(t, "secret_store_file", 1)
262	defer tc.Cleanup()
263	tc.G.Env.Test.SecretStorePrimingDisabled = false
264
265	td, cleanup := CreateReadOnlySecretStoreDir(tc)
266	defer cleanup()
267
268	mctx := NewMetaContextForTest(tc)
269	secretStore := NewSecretStoreFile(td)
270	err := PrimeSecretStore(mctx, secretStore)
271	require.Error(t, err)
272	require.Contains(t, err.Error(), "permission denied")
273}
274