github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/secret_store_secretservice.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  //go:build linux
     5  // +build linux
     6  
     7  package libkb
     8  
     9  import (
    10  	cryptorand "crypto/rand"
    11  	"crypto/sha256"
    12  	"encoding/hex"
    13  	"fmt"
    14  	"io"
    15  	"path/filepath"
    16  	"strings"
    17  	"time"
    18  
    19  	dbus "github.com/keybase/dbus"
    20  	secsrv "github.com/keybase/go-keychain/secretservice"
    21  	"golang.org/x/crypto/hkdf"
    22  )
    23  
    24  const sessionOpenTimeout = 5 * time.Second
    25  
    26  type SecretStoreRevokableSecretService struct{}
    27  
    28  var _ SecretStoreAll = (*SecretStoreRevokableSecretService)(nil)
    29  
    30  func NewSecretStoreRevokableSecretService() *SecretStoreRevokableSecretService {
    31  	return &SecretStoreRevokableSecretService{}
    32  }
    33  
    34  func (s *SecretStoreRevokableSecretService) makeServiceAttributes(mctx MetaContext) secsrv.Attributes {
    35  	attrs := secsrv.Attributes{
    36  		"service": mctx.G().Env.GetStoredSecretServiceName(),
    37  	}
    38  	if mctx.G().Env.GetRunMode() == DevelRunMode {
    39  		attrs["service-base"] = mctx.G().Env.GetStoredSecretServiceBaseName()
    40  	}
    41  	return attrs
    42  }
    43  
    44  func (s *SecretStoreRevokableSecretService) makeAttributes(mctx MetaContext, username NormalizedUsername, instanceIdentifier []byte) secsrv.Attributes {
    45  	serviceAttributes := s.makeServiceAttributes(mctx)
    46  	serviceAttributes["username"] = string(username)
    47  	serviceAttributes["identifier"] = hex.EncodeToString(instanceIdentifier)
    48  	serviceAttributes["note"] = "https://keybase.io/docs/crypto/local-key-security"
    49  	serviceAttributes["info"] = "Do not delete this entry. Instead, log out or uncheck 'remember passphrase' in the app."
    50  	return serviceAttributes
    51  }
    52  
    53  func (s *SecretStoreRevokableSecretService) retrieveManyItems(mctx MetaContext, srv *secsrv.SecretService, username NormalizedUsername, instanceIdentifier []byte) ([]dbus.ObjectPath, error) {
    54  	if srv == nil {
    55  		return nil, fmt.Errorf("got nil d-bus secretservice")
    56  	}
    57  	attributes := s.makeAttributes(mctx, username, instanceIdentifier)
    58  	items, err := srv.SearchCollection(secsrv.DefaultCollection, attributes)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	return items, nil
    63  }
    64  
    65  func (s *SecretStoreRevokableSecretService) maybeRetrieveSingleItem(mctx MetaContext, srv *secsrv.SecretService, username NormalizedUsername, instanceIdentifier []byte) (*dbus.ObjectPath, error) {
    66  	items, err := s.retrieveManyItems(mctx, srv, username, instanceIdentifier)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	if len(items) < 1 {
    72  		return nil, nil
    73  	}
    74  	if len(items) > 1 {
    75  		mctx.Warning("found more than one match in keyring for query %+v", s.makeAttributes(mctx, username, instanceIdentifier))
    76  	}
    77  	item := items[0]
    78  	err = srv.Unlock([]dbus.ObjectPath{item})
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	return &item, nil
    83  }
    84  
    85  func (s *SecretStoreRevokableSecretService) keystoreDir(mctx MetaContext, username string) string {
    86  	return fmt.Sprintf("ring%c%s", filepath.Separator, username)
    87  }
    88  
    89  func (s *SecretStoreRevokableSecretService) secretlessKeystore(mctx MetaContext, username string) SecretlessErasableKVStore {
    90  	return NewSecretlessFileErasableKVStore(mctx, s.keystoreDir(mctx, username))
    91  }
    92  
    93  func (s *SecretStoreRevokableSecretService) keystoreKey() string {
    94  	return "key"
    95  }
    96  
    97  func (s *SecretStoreRevokableSecretService) keystore(mctx MetaContext, username string, keyringSecret []byte) ErasableKVStore {
    98  	keygen := func(mctx MetaContext, noise NoiseBytes) (xs [32]byte, err error) {
    99  		// hkdf with salt=nil, info=context string, and using entropy from both
   100  		// the noise in the file and the secret in the keyring. Thus, when we
   101  		// try to erase this secret, as long as we are able to delete it from
   102  		// either the noise file or the keyring, we'll have succeeded in making
   103  		// the secret impossible to retrieve.
   104  		// See additional docs at https://keybase.io/docs/crypto/local-key-security.
   105  		h := hkdf.New(sha256.New, append(noise[:], keyringSecret...), nil, []byte(DeriveReasonLinuxRevokableKeyring))
   106  		_, err = io.ReadFull(h, xs[:])
   107  		if err != nil {
   108  			return [32]byte{}, err
   109  		}
   110  		return xs, nil
   111  	}
   112  	return NewFileErasableKVStore(mctx, s.keystoreDir(mctx, username), keygen)
   113  }
   114  
   115  const identifierKeystoreSuffix = ".user"
   116  
   117  func (s *SecretStoreRevokableSecretService) identifierKeystoreKey(username NormalizedUsername) string {
   118  	return string(username) + identifierKeystoreSuffix
   119  }
   120  
   121  func (s *SecretStoreRevokableSecretService) identifierKeystore(mctx MetaContext) ErasableKVStore {
   122  	plaintextKeygen := func(mctx MetaContext, noise NoiseBytes) (xs [32]byte, err error) {
   123  		return sha256.Sum256(noise[:]), nil
   124  	}
   125  	return NewFileErasableKVStore(mctx, "ring-identifiers", plaintextKeygen)
   126  }
   127  
   128  func (s *SecretStoreRevokableSecretService) RetrieveSecret(mctx MetaContext, username NormalizedUsername) (secret LKSecFullSecret, err error) {
   129  	defer mctx.Trace("SecretStoreRevokableSecretService.RetrieveSecret", &err)()
   130  
   131  	identifierKeystore := s.identifierKeystore(mctx)
   132  	var instanceIdentifier []byte
   133  	err = identifierKeystore.Get(mctx, s.identifierKeystoreKey(username), &instanceIdentifier)
   134  	if err != nil {
   135  		return LKSecFullSecret{}, err
   136  	}
   137  
   138  	srv, err := secsrv.NewService()
   139  	if err != nil {
   140  		return LKSecFullSecret{}, err
   141  	}
   142  	srv.SetSessionOpenTimeout(sessionOpenTimeout)
   143  	session, err := srv.OpenSession(secsrv.AuthenticationDHAES)
   144  	if err != nil {
   145  		return LKSecFullSecret{}, err
   146  	}
   147  	defer srv.CloseSession(session)
   148  
   149  	item, err := s.maybeRetrieveSingleItem(mctx, srv, username, instanceIdentifier)
   150  	if err != nil {
   151  		return LKSecFullSecret{}, err
   152  	}
   153  	if item == nil {
   154  		return LKSecFullSecret{}, fmt.Errorf("secret not found in secretstore")
   155  	}
   156  	keyringSecret, err := srv.GetSecret(*item, *session)
   157  	if err != nil {
   158  		return LKSecFullSecret{}, err
   159  	}
   160  
   161  	keystore := s.keystore(mctx, string(username), keyringSecret)
   162  	var secretBytes []byte
   163  	err = keystore.Get(mctx, s.keystoreKey(), &secretBytes)
   164  	if err != nil {
   165  		return LKSecFullSecret{}, err
   166  	}
   167  
   168  	return newLKSecFullSecretFromBytes(secretBytes)
   169  }
   170  
   171  func (s *SecretStoreRevokableSecretService) StoreSecret(mctx MetaContext, username NormalizedUsername, secret LKSecFullSecret) (err error) {
   172  	defer mctx.Trace("SecretStoreRevokableSecretService.StoreSecret", &err)()
   173  
   174  	// We add a public random identifier to the secret's properties in the
   175  	// Secret Service so if the same machine (with the same keyring) is storing
   176  	// passwords for the same user but in different home directories, they
   177  	// don't overwrite each others' keyring secrets (effectively logging the
   178  	// other one out after service restart).
   179  	instanceIdentifier := make([]byte, 32)
   180  	_, err = cryptorand.Read(instanceIdentifier)
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	keyringSecret := make([]byte, 32)
   186  	_, err = cryptorand.Read(keyringSecret)
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	srv, err := secsrv.NewService()
   192  	if err != nil {
   193  		return err
   194  	}
   195  	srv.SetSessionOpenTimeout(sessionOpenTimeout)
   196  	session, err := srv.OpenSession(secsrv.AuthenticationDHAES)
   197  	if err != nil {
   198  		return err
   199  	}
   200  	defer srv.CloseSession(session)
   201  	label := fmt.Sprintf("%s@%s", username, mctx.G().Env.GetStoredSecretServiceName())
   202  	properties := secsrv.NewSecretProperties(label, s.makeAttributes(mctx, username, instanceIdentifier))
   203  	srvSecret, err := session.NewSecret(keyringSecret)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	err = srv.Unlock([]dbus.ObjectPath{secsrv.DefaultCollection})
   208  	if err != nil {
   209  		return err
   210  	}
   211  	_, err = srv.CreateItem(secsrv.DefaultCollection, properties, srvSecret, secsrv.ReplaceBehaviorReplace)
   212  	if err != nil {
   213  		return err
   214  	}
   215  
   216  	identifierKeystore := s.identifierKeystore(mctx)
   217  	err = identifierKeystore.Put(mctx, s.identifierKeystoreKey(username), instanceIdentifier)
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	keystore := s.keystore(mctx, string(username), keyringSecret)
   223  	err = keystore.Put(mctx, s.keystoreKey(), secret.Bytes())
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	return nil
   229  }
   230  
   231  func (s *SecretStoreRevokableSecretService) ClearSecret(mctx MetaContext, username NormalizedUsername) (err error) {
   232  	defer mctx.Trace("SecretStoreRevokableSecretService.ClearSecret", &err)()
   233  
   234  	// Delete file-based portion first. If it fails, we can still try to erase the keyring's portion.
   235  	secretlessKeystore := s.secretlessKeystore(mctx, string(username))
   236  	keystoreErr := secretlessKeystore.Erase(mctx, s.keystoreKey())
   237  	if keystoreErr != nil {
   238  		mctx.Warning("Failed to erase keystore half: %s; attempting to delete from keyring", keystoreErr)
   239  	}
   240  
   241  	identifierKeystore := s.identifierKeystore(mctx)
   242  	var instanceIdentifier []byte
   243  	err = identifierKeystore.Get(mctx, s.identifierKeystoreKey(username), &instanceIdentifier)
   244  	if err != nil {
   245  		// If we can't get the identifier, we can't delete it from the keyring, so bail out here.
   246  		return CombineErrors(keystoreErr, err)
   247  	}
   248  
   249  	err = identifierKeystore.Erase(mctx, s.identifierKeystoreKey(username))
   250  	if err != nil {
   251  		// We can continue even if we failed to erase the identifier, since we know it now.
   252  		mctx.Warning("Failed to erase identifier from identifier keystore %s; continuing to attempt to delete from keyring", err)
   253  	}
   254  
   255  	srv, err := secsrv.NewService()
   256  	if err != nil {
   257  		return CombineErrors(keystoreErr, err)
   258  	}
   259  	srv.SetSessionOpenTimeout(sessionOpenTimeout)
   260  	// Only delete the ones for the identifier we care about, so as not to erase
   261  	// other passwords for the same user in a different home directory on the
   262  	// same computer.
   263  	items, err := s.retrieveManyItems(mctx, srv, username, instanceIdentifier)
   264  	if err != nil {
   265  		return CombineErrors(keystoreErr, err)
   266  	}
   267  	for _, item := range items {
   268  		err = srv.DeleteItem(item)
   269  		if err != nil {
   270  			return CombineErrors(keystoreErr, err)
   271  		}
   272  	}
   273  
   274  	return keystoreErr
   275  }
   276  
   277  // Note that in the case of corruption, not all of these usernames may actually
   278  // be able to be logged in as due to the noise file being corrupted, the
   279  // keyring being uninstalled, etc.
   280  func (s *SecretStoreRevokableSecretService) GetUsersWithStoredSecrets(mctx MetaContext) (usernames []string, err error) {
   281  	defer mctx.Trace("SecretStoreRevokableSecretService.GetUsersWithStoredSecrets", &err)()
   282  	identifierKeystore := s.identifierKeystore(mctx)
   283  	suffixedUsernames, err := identifierKeystore.AllKeys(mctx, identifierKeystoreSuffix)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  	for _, suffixedUsername := range suffixedUsernames {
   288  		usernames = append(usernames, strings.TrimSuffix(suffixedUsername, identifierKeystoreSuffix))
   289  	}
   290  	return usernames, nil
   291  }
   292  
   293  func (s *SecretStoreRevokableSecretService) GetOptions(MetaContext) *SecretStoreOptions  { return nil }
   294  func (s *SecretStoreRevokableSecretService) SetOptions(MetaContext, *SecretStoreOptions) {}