github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/secret_store_upgradeable_test.go (about)

     1  // Copyright 2019 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/require"
    10  )
    11  
    12  func makeRandomSecretForTest(t *testing.T) LKSecFullSecret {
    13  	randBytes, err := RandBytes(LKSecLen)
    14  	require.NoError(t, err)
    15  	secret, err := newLKSecFullSecretFromBytes(randBytes)
    16  	require.NoError(t, err)
    17  	return secret
    18  }
    19  
    20  type secretStoreUpgForTest struct {
    21  	store *SecretStoreUpgradeable
    22  	memA  *SecretStoreMem
    23  	memB  *SecretStoreMem
    24  
    25  	shouldFallback SecretStoreFallbackBehavior
    26  	shouldUpgrade  bool
    27  }
    28  
    29  func newSecretStoreUpgForTest() *secretStoreUpgForTest {
    30  	store := secretStoreUpgForTest{
    31  		memA: NewSecretStoreMem(),
    32  		memB: NewSecretStoreMem(),
    33  	}
    34  
    35  	shouldUpgradeOpportunistically := func() bool {
    36  		return store.shouldUpgrade
    37  	}
    38  	shouldStoreInFallback := func(options *SecretStoreOptions) SecretStoreFallbackBehavior {
    39  		return store.shouldFallback
    40  	}
    41  
    42  	store.store = NewSecretStoreUpgradeable(store.memA, store.memB, "memory primary", "memory secondary",
    43  		shouldUpgradeOpportunistically, shouldStoreInFallback)
    44  	return &store
    45  }
    46  
    47  func TestUSSUpgradeOnStore(t *testing.T) {
    48  	tc := SetupTest(t, "secret store ops", 1)
    49  	defer tc.Cleanup()
    50  
    51  	testStore := newSecretStoreUpgForTest()
    52  	testStore.shouldFallback = SecretStoreFallbackBehaviorAlways
    53  	testStore.shouldUpgrade = false
    54  	ss := testStore.store
    55  
    56  	m := NewMetaContextForTest(tc)
    57  	nu := NewNormalizedUsername("tusername")
    58  	secret := makeRandomSecretForTest(t)
    59  
    60  	for i := 0; i < 2; i++ {
    61  		t.Logf("Doing Store/Retrieve, attempt %d", i)
    62  		err := ss.StoreSecret(m, nu, secret)
    63  		require.NoError(t, err)
    64  
    65  		// Secret should go to secret store B, and not secret store A
    66  		// because of fallback behavior.
    67  		require.Len(t, testStore.memA.secrets, 0)
    68  		require.Len(t, testStore.memB.secrets, 1)
    69  
    70  		rSecret, err := ss.RetrieveSecret(m, nu)
    71  		require.NoError(t, err)
    72  		require.True(t, rSecret.Equal(secret))
    73  
    74  		// Retrieve does not upgrade because shouldUpgrade=false
    75  		require.Len(t, testStore.memA.secrets, 0)
    76  		require.Len(t, testStore.memB.secrets, 1)
    77  
    78  		// Try the whole thing twice to ensure consistent behaviour.
    79  	}
    80  
    81  	// Change fallback behavior, primary secret store can be used again.
    82  	testStore.shouldFallback = SecretStoreFallbackBehaviorOnError
    83  	for i := 0; i < 2; i++ {
    84  		// Not doing fallback anymore, store B should be cleared for NU and
    85  		// secret should be exclusively in store A.
    86  		t.Logf("shouldFallback = false, trying again, attempt %d", i)
    87  		err := ss.StoreSecret(m, nu, secret)
    88  		require.NoError(t, err)
    89  
    90  		require.Len(t, testStore.memA.secrets, 1)
    91  		require.Len(t, testStore.memB.secrets, 0)
    92  
    93  		rSecret, err := ss.RetrieveSecret(m, nu)
    94  		require.NoError(t, err)
    95  		require.True(t, rSecret.Equal(secret))
    96  	}
    97  }
    98  
    99  func TestUSSRetrieveWhenFallback(t *testing.T) {
   100  	tc := SetupTest(t, "secret store ops", 1)
   101  	defer tc.Cleanup()
   102  
   103  	testStore := newSecretStoreUpgForTest()
   104  	testStore.shouldFallback = SecretStoreFallbackBehaviorOnError
   105  	testStore.shouldUpgrade = true
   106  	ss := testStore.store
   107  
   108  	m := NewMetaContextForTest(tc)
   109  	nu := NewNormalizedUsername("tusername")
   110  	secret := makeRandomSecretForTest(t)
   111  
   112  	err := ss.StoreSecret(m, nu, secret)
   113  	require.NoError(t, err)
   114  
   115  	// Should store in primary secret store.
   116  	require.Len(t, testStore.memA.secrets, 1)
   117  	require.Len(t, testStore.memB.secrets, 0)
   118  
   119  	rSecret, err := ss.RetrieveSecret(m, nu)
   120  	require.NoError(t, err)
   121  	require.True(t, rSecret.Equal(secret))
   122  
   123  	// Enable fallback - assume user changed system settings / configuration.
   124  	testStore.shouldFallback = SecretStoreFallbackBehaviorAlways
   125  
   126  	// Retrieve should still find the secret in primary store.
   127  	rSecret, err = ss.RetrieveSecret(m, nu)
   128  	require.NoError(t, err)
   129  	require.True(t, rSecret.Equal(secret))
   130  
   131  	require.Len(t, testStore.memA.secrets, 1)
   132  	require.Len(t, testStore.memB.secrets, 0)
   133  
   134  	// StoreSecret will skip primary store and store the secret in secondary
   135  	// store. So it will be stored in both.
   136  	secret2 := makeRandomSecretForTest(t)
   137  	err = ss.StoreSecret(m, nu, secret2)
   138  	require.NoError(t, err)
   139  
   140  	require.Len(t, testStore.memA.secrets, 1)
   141  	require.Len(t, testStore.memB.secrets, 1)
   142  
   143  	// Retrieve still works.
   144  	// TODO: Bug - retrieve still retrieves from primary store.
   145  	rSecret, err = ss.RetrieveSecret(m, nu)
   146  	require.NoError(t, err)
   147  	require.True(t, rSecret.Equal(secret))
   148  }
   149  
   150  func TestUSSOpportunisticUpgrade(t *testing.T) {
   151  	tc := SetupTest(t, "secret store ops", 1)
   152  	defer tc.Cleanup()
   153  
   154  	testStore := newSecretStoreUpgForTest()
   155  	testStore.shouldFallback = SecretStoreFallbackBehaviorAlways
   156  	testStore.shouldUpgrade = true
   157  	ss := testStore.store
   158  
   159  	m := NewMetaContextForTest(tc)
   160  	nu := NewNormalizedUsername("tusername")
   161  	secret := makeRandomSecretForTest(t)
   162  
   163  	t.Logf("Storing secret with fallback=Always")
   164  	err := ss.StoreSecret(m, nu, secret)
   165  	require.NoError(t, err)
   166  
   167  	// Secret should go to secret store B, and not secret store A
   168  	// because we shouldStoreInFallback returns true.
   169  	require.Len(t, testStore.memA.secrets, 0)
   170  	require.Len(t, testStore.memB.secrets, 1)
   171  
   172  	t.Logf("Retrieving secret with fallback=Always")
   173  	rSecret, err := ss.RetrieveSecret(m, nu)
   174  	require.NoError(t, err)
   175  	require.True(t, rSecret.Equal(secret))
   176  
   177  	// We are still in fallback mode, so upgrade should not happen after last
   178  	// retrieval.
   179  	require.Len(t, testStore.memA.secrets, 0)
   180  	require.Len(t, testStore.memB.secrets, 1)
   181  
   182  	// Change shouldFallback to OnError (user upgraded their machine / settings
   183  	// for example).
   184  	testStore.shouldFallback = SecretStoreFallbackBehaviorOnError
   185  
   186  	t.Logf("Changed shouldFallback to OnError, trying to retrieve")
   187  	rSecret, err = ss.RetrieveSecret(m, nu)
   188  	require.NoError(t, err)
   189  	require.True(t, rSecret.Equal(secret))
   190  
   191  	// Retrieving secret should have upgraded us to store A.
   192  	require.Len(t, testStore.memA.secrets, 1)
   193  	require.Len(t, testStore.memB.secrets, 0)
   194  
   195  	// Try to retrieve again, should retrieve exclusively from primary secret
   196  	// store.
   197  	t.Logf("Retrieving again")
   198  	rSecret, err = ss.RetrieveSecret(m, nu)
   199  	require.NoError(t, err)
   200  	require.True(t, rSecret.Equal(secret))
   201  }
   202  
   203  func TestUSSFallback(t *testing.T) {
   204  	tc := SetupTest(t, "secret store ops", 1)
   205  	defer tc.Cleanup()
   206  
   207  	failA := NewSecretStoreFail()
   208  	memB := NewSecretStoreMem()
   209  
   210  	behavior := SecretStoreFallbackBehaviorOnError
   211  	shouldUpgradeOpportunistically := func() bool {
   212  		return true
   213  	}
   214  	shouldStoreInFallback := func(options *SecretStoreOptions) SecretStoreFallbackBehavior {
   215  		return behavior
   216  	}
   217  
   218  	store := NewSecretStoreUpgradeable(failA, memB, "failure primary", "memory secondary",
   219  		shouldUpgradeOpportunistically, shouldStoreInFallback)
   220  
   221  	m := NewMetaContextForTest(tc)
   222  	nu := NewNormalizedUsername("tusername")
   223  	secret := makeRandomSecretForTest(t)
   224  
   225  	err := store.StoreSecret(m, nu, secret)
   226  	require.NoError(t, err)
   227  	require.Len(t, memB.secrets, 1)
   228  
   229  	rSecret, err := store.RetrieveSecret(m, nu)
   230  	require.NoError(t, err)
   231  	require.True(t, rSecret.Equal(secret))
   232  
   233  	t.Logf("Changing behavior to SecretStoreFallbackBehaviorNever")
   234  	behavior = SecretStoreFallbackBehaviorNever
   235  
   236  	for i := 0; i < 2; i++ {
   237  		t.Logf("Attempt %d", i)
   238  
   239  		// We should still be able to retrieve our secret.
   240  		rSecret, err := store.RetrieveSecret(m, nu)
   241  		require.NoError(t, err)
   242  		require.True(t, rSecret.Equal(secret))
   243  
   244  		// But we can't store a new one.
   245  		err = store.StoreSecret(m, nu, secret)
   246  		require.Error(t, err)
   247  
   248  		// Still has old secret.
   249  		require.Len(t, memB.secrets, 1)
   250  
   251  		// Try this twice, to be sure that:
   252  		// - first retrieval does not affect subsequent ones.
   253  		// - failed StoreSecret to primary secret store does not affect subsequent
   254  		// retrievals from fallback.
   255  	}
   256  
   257  	// Clear should clear fallback store as well, despite behavior setting.
   258  	t.Logf("Trying to ClearSecret")
   259  	err = store.ClearSecret(m, nu)
   260  	require.NoError(t, err)
   261  	require.Len(t, memB.secrets, 0)
   262  }
   263  
   264  func TestUSSBothFail(t *testing.T) {
   265  	tc := SetupTest(t, "secret store ops", 1)
   266  	defer tc.Cleanup()
   267  
   268  	failA := NewSecretStoreFail()
   269  	failB := NewSecretStoreFail()
   270  
   271  	shouldUpgradeOpportunistically := func() bool {
   272  		return true
   273  	}
   274  	shouldStoreInFallback := func(options *SecretStoreOptions) SecretStoreFallbackBehavior {
   275  		return SecretStoreFallbackBehaviorOnError
   276  	}
   277  
   278  	store := NewSecretStoreUpgradeable(failA, failB, "fail primary", "fail secondary",
   279  		shouldUpgradeOpportunistically, shouldStoreInFallback)
   280  
   281  	m := NewMetaContextForTest(tc)
   282  	nu := NewNormalizedUsername("tusername")
   283  	secret := makeRandomSecretForTest(t)
   284  
   285  	err := store.StoreSecret(m, nu, secret)
   286  	require.Error(t, err)
   287  
   288  	_, err = store.RetrieveSecret(m, nu)
   289  	require.Error(t, err)
   290  
   291  	// Clear returns an error when both stores fail to clear.
   292  	err = store.ClearSecret(m, nu)
   293  	require.Error(t, err)
   294  }