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 }