github.com/decred/dcrlnd@v0.7.6/macaroons/store_test.go (about) 1 package macaroons_test 2 3 import ( 4 "context" 5 "io/ioutil" 6 "os" 7 "path" 8 "testing" 9 10 "github.com/decred/dcrlnd/internal/snacl" 11 "github.com/decred/dcrlnd/kvdb" 12 "github.com/decred/dcrlnd/macaroons" 13 14 "github.com/stretchr/testify/require" 15 ) 16 17 var ( 18 defaultRootKeyIDContext = macaroons.ContextWithRootKeyID( 19 context.Background(), macaroons.DefaultRootKeyID, 20 ) 21 ) 22 23 // newTestStore creates a new bolt DB in a temporary directory and then 24 // initializes a root key storage for that DB. 25 func newTestStore(t *testing.T) (string, func(), *macaroons.RootKeyStorage) { 26 tempDir, err := ioutil.TempDir("", "macaroonstore-") 27 require.NoError(t, err) 28 29 cleanup, store := openTestStore(t, tempDir) 30 cleanup2 := func() { 31 cleanup() 32 _ = os.RemoveAll(tempDir) 33 } 34 35 return tempDir, cleanup2, store 36 } 37 38 // openTestStore opens an existing bolt DB and then initializes a root key 39 // storage for that DB. 40 func openTestStore(t *testing.T, tempDir string) (func(), 41 *macaroons.RootKeyStorage) { 42 43 db, err := kvdb.Create( 44 kvdb.BoltBackendName, path.Join(tempDir, "weks.db"), true, 45 kvdb.DefaultDBTimeout, 46 ) 47 require.NoError(t, err) 48 49 store, err := macaroons.NewRootKeyStorage(db) 50 if err != nil { 51 _ = db.Close() 52 t.Fatalf("Error creating root key store: %v", err) 53 } 54 55 cleanup := func() { 56 _ = store.Close() 57 _ = db.Close() 58 } 59 60 return cleanup, store 61 } 62 63 // TestStore tests the normal use cases of the store like creating, unlocking, 64 // reading keys and closing it. 65 func TestStore(t *testing.T) { 66 tempDir, cleanup, store := newTestStore(t) 67 defer cleanup() 68 69 _, _, err := store.RootKey(context.TODO()) 70 require.Equal(t, macaroons.ErrStoreLocked, err) 71 72 _, err = store.Get(context.TODO(), nil) 73 require.Equal(t, macaroons.ErrStoreLocked, err) 74 75 pw := []byte("weks") 76 err = store.CreateUnlock(&pw) 77 require.NoError(t, err) 78 79 // Check ErrContextRootKeyID is returned when no root key ID found in 80 // context. 81 _, _, err = store.RootKey(context.TODO()) 82 require.Equal(t, macaroons.ErrContextRootKeyID, err) 83 84 // Check ErrMissingRootKeyID is returned when empty root key ID is used. 85 emptyKeyID := make([]byte, 0) 86 badCtx := macaroons.ContextWithRootKeyID(context.TODO(), emptyKeyID) 87 _, _, err = store.RootKey(badCtx) 88 require.Equal(t, macaroons.ErrMissingRootKeyID, err) 89 90 // Create a context with illegal root key ID value. 91 encryptedKeyID := []byte("enckey") 92 badCtx = macaroons.ContextWithRootKeyID(context.TODO(), encryptedKeyID) 93 _, _, err = store.RootKey(badCtx) 94 require.Equal(t, macaroons.ErrKeyValueForbidden, err) 95 96 // Create a context with root key ID value. 97 key, id, err := store.RootKey(defaultRootKeyIDContext) 98 require.NoError(t, err) 99 100 rootID := id 101 require.Equal(t, macaroons.DefaultRootKeyID, rootID) 102 103 key2, err := store.Get(defaultRootKeyIDContext, id) 104 require.NoError(t, err) 105 require.Equal(t, key, key2) 106 107 badpw := []byte("badweks") 108 err = store.CreateUnlock(&badpw) 109 require.Equal(t, macaroons.ErrAlreadyUnlocked, err) 110 111 _ = store.Close() 112 _ = store.Backend.Close() 113 114 // Between here and the re-opening of the store, it's possible to get 115 // a double-close, but that's not such a big deal since the tests will 116 // fail anyway in that case. 117 _, store = openTestStore(t, tempDir) 118 119 err = store.CreateUnlock(&badpw) 120 require.Equal(t, snacl.ErrInvalidPassword, err) 121 122 err = store.CreateUnlock(nil) 123 require.Equal(t, macaroons.ErrPasswordRequired, err) 124 125 _, _, err = store.RootKey(defaultRootKeyIDContext) 126 require.Equal(t, macaroons.ErrStoreLocked, err) 127 128 _, err = store.Get(defaultRootKeyIDContext, nil) 129 require.Equal(t, macaroons.ErrStoreLocked, err) 130 131 err = store.CreateUnlock(&pw) 132 require.NoError(t, err) 133 134 key, err = store.Get(defaultRootKeyIDContext, rootID) 135 require.NoError(t, err) 136 require.Equal(t, key, key2) 137 138 key, id, err = store.RootKey(defaultRootKeyIDContext) 139 require.NoError(t, err) 140 require.Equal(t, key, key2) 141 require.Equal(t, rootID, id) 142 } 143 144 // TestStoreGenerateNewRootKey tests that a root key can be replaced with a new 145 // one in the store without changing the password. 146 func TestStoreGenerateNewRootKey(t *testing.T) { 147 _, cleanup, store := newTestStore(t) 148 defer cleanup() 149 150 // The store must be unlocked to replace the root key. 151 err := store.GenerateNewRootKey() 152 require.Equal(t, macaroons.ErrStoreLocked, err) 153 154 // Unlock the store and read the current key. 155 pw := []byte("weks") 156 err = store.CreateUnlock(&pw) 157 require.NoError(t, err) 158 oldRootKey, _, err := store.RootKey(defaultRootKeyIDContext) 159 require.NoError(t, err) 160 161 // Replace the root key with a new random key. 162 err = store.GenerateNewRootKey() 163 require.NoError(t, err) 164 165 // Finally, read the root key from the DB and compare it to the one 166 // we got returned earlier. This makes sure that the encryption/ 167 // decryption of the key in the DB worked as expected too. 168 newRootKey, _, err := store.RootKey(defaultRootKeyIDContext) 169 require.NoError(t, err) 170 require.NotEqual(t, oldRootKey, newRootKey) 171 } 172 173 // TestStoreChangePassword tests that the password for the store can be changed 174 // without changing the root key. 175 func TestStoreChangePassword(t *testing.T) { 176 tempDir, cleanup, store := newTestStore(t) 177 defer cleanup() 178 179 // The store must be unlocked to replace the root key. 180 err := store.ChangePassword(nil, nil) 181 require.Equal(t, macaroons.ErrStoreLocked, err) 182 183 // Unlock the DB and read the current root key. This will need to stay 184 // the same after changing the password for the test to succeed. 185 pw := []byte("weks") 186 err = store.CreateUnlock(&pw) 187 require.NoError(t, err) 188 rootKey, _, err := store.RootKey(defaultRootKeyIDContext) 189 require.NoError(t, err) 190 191 // Both passwords must be set. 192 err = store.ChangePassword(nil, nil) 193 require.Equal(t, macaroons.ErrPasswordRequired, err) 194 195 // Make sure that an error is returned if we try to change the password 196 // without the correct old password. 197 wrongPw := []byte("wrong") 198 newPw := []byte("newpassword") 199 err = store.ChangePassword(wrongPw, newPw) 200 require.Equal(t, snacl.ErrInvalidPassword, err) 201 202 // Now really do change the password. 203 err = store.ChangePassword(pw, newPw) 204 require.NoError(t, err) 205 206 // Close the store. This will close the underlying DB and we need to 207 // create a new store instance. Let's make sure we can't use it again 208 // after closing. 209 err = store.Close() 210 require.NoError(t, err) 211 err = store.Backend.Close() 212 require.NoError(t, err) 213 214 err = store.CreateUnlock(&newPw) 215 require.Error(t, err) 216 217 // Let's open it again and try unlocking with the new password. 218 _, store = openTestStore(t, tempDir) 219 err = store.CreateUnlock(&newPw) 220 require.NoError(t, err) 221 222 // Finally read the root key from the DB using the new password and 223 // make sure the root key stayed the same. 224 rootKeyDb, _, err := store.RootKey(defaultRootKeyIDContext) 225 require.NoError(t, err) 226 require.Equal(t, rootKey, rootKeyDb) 227 }