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