github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/secret_store.go (about)

     1  // Copyright 2015 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  	"errors"
     8  	"fmt"
     9  	"sort"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    15  )
    16  
    17  type SecretRetriever interface {
    18  	RetrieveSecret(m MetaContext) (LKSecFullSecret, error)
    19  }
    20  
    21  type SecretStoreOptions struct {
    22  	RandomPw bool
    23  }
    24  
    25  func DefaultSecretStoreOptions() SecretStoreOptions {
    26  	return SecretStoreOptions{}
    27  }
    28  
    29  type SecretStorer interface {
    30  	StoreSecret(m MetaContext, secret LKSecFullSecret) error
    31  }
    32  
    33  // SecretStore stores/retreives the keyring-resident secrets for a given user.
    34  type 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.
    43  type 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.
    54  type SecretStoreImp struct {
    55  	username NormalizedUsername
    56  	store    *SecretStoreLocked
    57  	secret   LKSecFullSecret
    58  	sync.Mutex
    59  }
    60  
    61  var _ SecretStore = (*SecretStoreImp)(nil)
    62  
    63  func (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  
    78  func (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  
    87  func (s *SecretStoreImp) GetOptions(mctx MetaContext) *SecretStoreOptions {
    88  	if s.store != nil {
    89  		return s.store.GetOptions(mctx)
    90  	}
    91  	return nil
    92  
    93  }
    94  func (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.
   103  func 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  
   115  func 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  
   209  func 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  
   217  func 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.
   230  type SecretStoreLocked struct {
   231  	sync.Mutex
   232  	mem  SecretStoreAll
   233  	disk SecretStoreAll
   234  }
   235  
   236  func 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  
   245  func (s *SecretStoreLocked) isNil() bool {
   246  	return s.mem == nil && s.disk == nil
   247  }
   248  
   249  func (s *SecretStoreLocked) ClearMem() {
   250  	s.mem = NewSecretStoreMem()
   251  }
   252  
   253  func (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  
   282  func (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  
   302  func (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  
   325  func (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  
   358  func (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  
   376  func (s *SecretStoreLocked) IsPersistent() bool {
   377  	if s == nil || s.isNil() {
   378  		return false
   379  	}
   380  	return s.disk != nil
   381  }
   382  
   383  func (s *SecretStoreLocked) GetOptions(mctx MetaContext) *SecretStoreOptions {
   384  	if s.disk != nil {
   385  		return s.disk.GetOptions(mctx)
   386  	}
   387  	return nil
   388  }
   389  func (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
   398  func 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.ReplaceAll(testUsername, "=", ""))
   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  
   465  func 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  
   490  type loginTimeMap map[NormalizedUsername]time.Time
   491  
   492  func 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  
   500  func 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  
   511  func 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  }