github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/saltpackkeys/saltpack_recipient_keyfinder_engine.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 saltpackkeys
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/keybase/client/go/engine"
    10  	"github.com/keybase/client/go/externals"
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/teams"
    13  	"github.com/keybase/saltpack"
    14  
    15  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    16  )
    17  
    18  // SaltpackRecipientKeyfinderEngine is an engine to find Per User/Per Team Keys.
    19  // Users can also be loaded by assertions, possibly tracking them if necessary.
    20  //
    21  // SaltpackRecipientKeyfinderEngine extends the functionality of engine.SaltpackUserKeyfinder (which can only find user keys but not team keys).
    22  // This is a separate object (and also not part of the engine package) to avoid circular dependencies (as teams depends on engine).
    23  type SaltpackRecipientKeyfinderEngine struct {
    24  	engine.SaltpackUserKeyfinder
    25  	SymmetricEntityKeyMap map[keybase1.TeamID](keybase1.TeamApplicationKey)
    26  	SaltpackSymmetricKeys []libkb.SaltpackReceiverSymmetricKey
    27  }
    28  
    29  var _ libkb.Engine2 = (*SaltpackRecipientKeyfinderEngine)(nil)
    30  var _ libkb.SaltpackRecipientKeyfinderEngineInterface = (*SaltpackRecipientKeyfinderEngine)(nil)
    31  
    32  // SaltpackRecipientKeyfinderEngine creates a SaltpackRecipientKeyfinderEngine engine.
    33  func NewSaltpackRecipientKeyfinderEngineAsInterface(arg libkb.SaltpackRecipientKeyfinderArg) libkb.SaltpackRecipientKeyfinderEngineInterface {
    34  	return &SaltpackRecipientKeyfinderEngine{
    35  		SaltpackUserKeyfinder: *engine.NewSaltpackUserKeyfinder(arg),
    36  		SymmetricEntityKeyMap: make(map[keybase1.TeamID](keybase1.TeamApplicationKey)),
    37  	}
    38  }
    39  
    40  // Name is the unique engine name.
    41  func (e *SaltpackRecipientKeyfinderEngine) Name() string {
    42  	return "SaltpackRecipientKeyfinder"
    43  }
    44  
    45  func (e *SaltpackRecipientKeyfinderEngine) Run(m libkb.MetaContext) (err error) {
    46  	defer m.Trace("SaltpackRecipientKeyfinder#Run", &err)()
    47  
    48  	err = e.AddOwnKeysIfNeeded(m)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	err = e.identifyAndAddRecipients(m)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	err = e.uploadKeyPseudonymsAndGenerateSymmetricKeys(m)
    59  
    60  	return err
    61  }
    62  
    63  func (e *SaltpackRecipientKeyfinderEngine) GetSymmetricKeys() []libkb.SaltpackReceiverSymmetricKey {
    64  	return e.SaltpackSymmetricKeys
    65  }
    66  
    67  func (e *SaltpackRecipientKeyfinderEngine) uploadKeyPseudonymsAndGenerateSymmetricKeys(m libkb.MetaContext) error {
    68  	// Fetch the keys and assemble the pseudonym info objects.
    69  	var pseudonymInfos []libkb.KeyPseudonymInfo
    70  	for teamID, appKey := range e.SymmetricEntityKeyMap {
    71  		pseudonymInfo := libkb.KeyPseudonymInfo{
    72  			ID:          teamID.AsUserOrTeam(),
    73  			Application: appKey.Application,
    74  			KeyGen:      libkb.KeyGen(appKey.KeyGeneration),
    75  			Nonce:       libkb.RandomPseudonymNonce(),
    76  		}
    77  		pseudonymInfos = append(pseudonymInfos, pseudonymInfo)
    78  	}
    79  
    80  	// Post the pseudonyms in a batch. This will populate the KeyPseudonym field of each element of pseudonymInfos
    81  	err := libkb.MakeAndPostKeyPseudonyms(m, &pseudonymInfos)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	for _, pseudonymInfo := range pseudonymInfos {
    87  		e.SaltpackSymmetricKeys = append(e.SaltpackSymmetricKeys, libkb.SaltpackReceiverSymmetricKey{
    88  			Key:        saltpack.SymmetricKey(e.SymmetricEntityKeyMap[keybase1.TeamID(pseudonymInfo.ID)].Key),
    89  			Identifier: pseudonymInfo.KeyPseudonym[:],
    90  		})
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  // identifyAndAddRecipients adds the KID corresponding to each recipient to the recipientMap
    97  func (e *SaltpackRecipientKeyfinderEngine) identifyAndAddRecipients(m libkb.MetaContext) error {
    98  	// TODO make these lookups in parallel (maybe using sync.WaitGroup)
    99  	for _, u := range e.Arg.Recipients {
   100  		err := e.identifyAndAddUserRecipient(m, u)
   101  		if err != nil {
   102  			return err
   103  		}
   104  	}
   105  	for _, u := range e.Arg.TeamRecipients {
   106  		err := e.lookupAndAddTeam(m, u)
   107  		if err != nil {
   108  			return err
   109  		}
   110  	}
   111  	return nil
   112  }
   113  
   114  func (e *SaltpackRecipientKeyfinderEngine) addPUKOrImplicitTeamKeys(m libkb.MetaContext, upk *keybase1.UserPlusKeysV2) error {
   115  	err := e.AddPUK(m, upk)
   116  	if err == nil {
   117  		return nil
   118  	}
   119  	if m.ActiveDevice().Valid() {
   120  		m.Debug("user %v (%v) does not have a PUK, adding the implicit team key instead", upk.Username, upk.Uid)
   121  		err = e.lookupAndAddImplicitTeamKeys(m, upk.Username)
   122  		return err
   123  	}
   124  	m.Debug("user %v (%v) does not have a PUK, and there is no logged in user, so we cannot resort to implicit teams", upk.Username, upk.Uid)
   125  	return libkb.NewLoginRequiredError(fmt.Sprintf("Encrypting for %v requires logging in", upk.Username))
   126  }
   127  
   128  // identifyAndAddUserRecipient add the KID corresponding to a recipient to the recipientMap
   129  func (e *SaltpackRecipientKeyfinderEngine) identifyAndAddUserRecipient(m libkb.MetaContext, u string) (err error) {
   130  	upk, err := e.IdentifyUser(m, u) // For existing users
   131  	switch {
   132  	case err == nil:
   133  		// nothing to do here
   134  	case libkb.IsIdentifyProofError(err):
   135  		return fmt.Errorf("Cannot encrypt for %v as their account has changed since you last followed them (it might have been compromised!): please review their identity (with `keybase follow %v`) and then try again (err = %v)", u, u, err)
   136  	case libkb.IsNotFoundError(err) || libkb.IsResolutionNotFoundError(err):
   137  		// recipient is not a keybase user
   138  
   139  		expr, err := externals.AssertionParse(m, u)
   140  		if err != nil {
   141  			m.Debug("error parsing assertion: %s", err)
   142  			return libkb.NewRecipientNotFoundError(fmt.Sprintf("Cannot encrypt for %v: it is not a keybase user or social assertion (err = %v)", u, err))
   143  		}
   144  		if _, err := expr.ToSocialAssertion(); err != nil {
   145  			m.Debug("not a social assertion: %s (%s), err: %+v", u, expr, err)
   146  			return libkb.NewRecipientNotFoundError(fmt.Sprintf("Cannot encrypt for %v: it is not a keybase user or social assertion (err = %v)", u, err))
   147  		}
   148  
   149  		if !m.ActiveDevice().Valid() {
   150  			return libkb.NewRecipientNotFoundError(fmt.Sprintf("Cannot encrypt for %v: it is not a registered user (cannot encrypt for users not yet on keybase unless you are logged in)", u))
   151  		}
   152  		if !e.Arg.UseEntityKeys {
   153  			return libkb.NewRecipientNotFoundError(fmt.Sprintf("Cannot encrypt for %v: it is not a registered user (you can remove `--no-entity-keys` for users not yet on keybase)", u))
   154  		}
   155  		if e.Arg.NoSelfEncrypt {
   156  			return libkb.NewRecipientNotFoundError(fmt.Sprintf("Cannot encrypt for %v: it is not a registered user (you can remove `--no-self-encrypt` for users not yet on keybase)", u))
   157  		}
   158  
   159  		m.Debug("%q is not an existing user, trying to create an implicit team", u)
   160  		err = e.lookupAndAddImplicitTeamKeys(m, u)
   161  		return err
   162  	case libkb.IsNoKeyError(err):
   163  		// User exists but has no keys. Just try adding implicit team keys.
   164  		return e.lookupAndAddImplicitTeamKeys(m, u)
   165  	case libkb.IsAssertionParseErrorWithReason(err, libkb.AssertionParseErrorReasonUnexpectedOR):
   166  		return err
   167  	default:
   168  		return fmt.Errorf("Error while adding keys for %v: %v", u, err)
   169  	}
   170  
   171  	err = e.AddDeviceAndPaperKeys(m, upk)
   172  	err2 := e.addPUKOrImplicitTeamKeys(m, upk)
   173  	// If we managed to add at least one key for upk, we are happy.
   174  	if (!(e.Arg.UseDeviceKeys || e.Arg.UsePaperKeys) || err != nil) && (!e.Arg.UseEntityKeys || err2 != nil) {
   175  		return libkb.PickFirstError(err, err2)
   176  	}
   177  	return nil
   178  }
   179  
   180  func (e *SaltpackRecipientKeyfinderEngine) lookupAndAddTeam(m libkb.MetaContext, teamName string) error {
   181  	team, err := teams.Load(m.Ctx(), m.G(), keybase1.LoadTeamArg{
   182  		Name: teamName,
   183  	})
   184  	if err != nil {
   185  		return teams.FixupTeamGetError(m.Ctx(), m.G(), err, teamName, false /* public bool: this might not be true, but the message is less specific for private teams */)
   186  	}
   187  
   188  	// Test that the logged in user is part of the team, as a user can load a public team that they are not part of (and therefore have no keys for).
   189  	arg := libkb.NewLoadUserArgWithMetaContext(m).WithUID(m.ActiveDevice().UID()).WithForcePoll(true)
   190  	upak, _, err := m.G().GetUPAKLoader().LoadV2(arg)
   191  	if err != nil {
   192  		return err
   193  	}
   194  	if !team.IsMember(m.Ctx(), upak.Current.ToUserVersion()) {
   195  		return fmt.Errorf("cannot encrypt for team %s because you are not a member", teamName)
   196  	}
   197  
   198  	// Note: when we encrypt for a team with UseEntityKeys set, we use just the per team key, and do not add
   199  	// all the per user keys of the individual members (except for the sender's PUK, which is added unless NoSelfEncrypt is set).
   200  	if e.Arg.UseEntityKeys {
   201  		if e.Arg.UseRepudiableAuth {
   202  			return fmt.Errorf("encrypting for a team with --auth-type=repudiable requires --no-entity-keys")
   203  		}
   204  		appKey, err := team.SaltpackEncryptionKeyLatest(m.Ctx())
   205  		if err != nil {
   206  			return err
   207  		}
   208  		m.Debug("Adding team key for team %v", teamName)
   209  		e.SymmetricEntityKeyMap[team.ID] = appKey
   210  	}
   211  
   212  	if e.Arg.UseDeviceKeys || e.Arg.UsePaperKeys {
   213  		members, err := team.Members()
   214  		if err != nil {
   215  			return err
   216  		}
   217  		upakLoader := m.G().GetUPAKLoader()
   218  
   219  		for _, userVersion := range members.AllUserVersions() {
   220  			uid := userVersion.Uid
   221  			if e.Arg.NoSelfEncrypt && m.CurrentUID() == uid {
   222  				m.Debug("skipping device and paper keys for %v as part of team %v because of NoSelfEncrypt", uid, teamName)
   223  				continue
   224  			}
   225  			arg := libkb.NewLoadUserArgWithMetaContext(m).WithUID(uid).WithForcePoll(true).WithPublicKeyOptional()
   226  			upak, _, err := upakLoader.LoadV2(arg)
   227  			if err != nil {
   228  				return err
   229  			}
   230  			// Skip deleted and reset users
   231  			if upak.Current.Status == keybase1.StatusCode_SCDeleted {
   232  				m.Debug("skipping device and paper keys for %v as part of team %v because it is deleted", uid, teamName)
   233  				continue
   234  			}
   235  			if !userVersion.Eq(upak.Current.ToUserVersion()) {
   236  				m.Debug("skipping device and paper keys for %v as part of team %v because the user version doesn't match", uid, teamName)
   237  				continue
   238  			}
   239  
   240  			err = e.AddDeviceAndPaperKeys(m, &upak.Current)
   241  			if err != nil {
   242  				m.Debug("failed to add device and paper keys for %v as part of team %v, continuing...")
   243  			}
   244  		}
   245  	}
   246  
   247  	return err
   248  }
   249  
   250  func (e *SaltpackRecipientKeyfinderEngine) lookupAndAddImplicitTeamKeys(m libkb.MetaContext, validSocialAssertionOrExistingUser string) (err error) {
   251  	// Implicit teams require login.
   252  	if !m.ActiveDevice().Valid() {
   253  		return libkb.NewLoginRequiredError(fmt.Sprintf("encrypting for %v requires login", validSocialAssertionOrExistingUser))
   254  	}
   255  	if !e.Arg.UseEntityKeys {
   256  		return fmt.Errorf("cannot encrypt for %v unless the --no-entity-keys option is turned off", validSocialAssertionOrExistingUser)
   257  	}
   258  	if e.Arg.UseRepudiableAuth {
   259  		return fmt.Errorf("cannot encrypt for %v with --auth-type=repudiable", validSocialAssertionOrExistingUser)
   260  	}
   261  	if e.Arg.NoSelfEncrypt {
   262  		return libkb.NewRecipientNotFoundError(fmt.Sprintf("cannot encrypt for %v with --no-self-encrypt", validSocialAssertionOrExistingUser))
   263  	}
   264  
   265  	team, _, impTeamName, err := teams.LookupOrCreateImplicitTeam(m.Ctx(), m.G(), m.CurrentUsername().String()+","+validSocialAssertionOrExistingUser, false)
   266  
   267  	if err != nil {
   268  		return err
   269  	}
   270  
   271  	appKey, err := team.SaltpackEncryptionKeyLatest(m.Ctx())
   272  	if err != nil {
   273  		return err
   274  	}
   275  	m.Debug("adding team key for implicit team %v", impTeamName)
   276  	e.UsingSBS = true
   277  	e.SBSAssertion = validSocialAssertionOrExistingUser
   278  	e.SymmetricEntityKeyMap[team.ID] = appKey
   279  
   280  	return nil
   281  }