1// Copyright 2016 Keybase Inc. All rights reserved.
2// Use of this source code is governed by a BSD
3// license that can be found in the LICENSE file.
4
5package libkbfs
6
7import (
8	"context"
9	"fmt"
10	"time"
11
12	lru "github.com/hashicorp/golang-lru"
13	"github.com/keybase/client/go/kbfs/idutil"
14	"github.com/keybase/client/go/kbfs/kbfscrypto"
15	"github.com/keybase/client/go/kbfs/kbfsmd"
16	"github.com/keybase/client/go/kbfs/tlf"
17	kbname "github.com/keybase/client/go/kbun"
18	"github.com/keybase/client/go/libkb"
19	"github.com/keybase/client/go/logger"
20	"github.com/keybase/client/go/protocol/keybase1"
21)
22
23const (
24	idToUserCacheSize = 50
25)
26
27// keybaseServiceOwner is a wrapper around a KeybaseService, to allow
28// switching the underlying service at runtime. It is usually
29// implemented by Config.
30type keybaseServiceOwner interface {
31	KeybaseService() KeybaseService
32}
33
34// KBPKIClient uses a KeybaseService.
35type KBPKIClient struct {
36	serviceOwner  keybaseServiceOwner
37	log           logger.Logger
38	idToUserCache *lru.Cache
39}
40
41var _ KBPKI = (*KBPKIClient)(nil)
42
43// NewKBPKIClient returns a new KBPKIClient with the given service.
44func NewKBPKIClient(
45	serviceOwner keybaseServiceOwner, log logger.Logger) *KBPKIClient {
46	cache, err := lru.New(idToUserCacheSize)
47	if err != nil {
48		cache = nil
49		log.CDebugf(context.TODO(), "Error creating LRU cache: %+v", err)
50	}
51
52	return &KBPKIClient{serviceOwner, log, cache}
53}
54
55// GetCurrentSession implements the KBPKI interface for KBPKIClient.
56func (k *KBPKIClient) GetCurrentSession(ctx context.Context) (
57	idutil.SessionInfo, error) {
58	const sessionID = 0
59	return k.serviceOwner.KeybaseService().CurrentSession(ctx, sessionID)
60}
61
62// Resolve implements the KBPKI interface for KBPKIClient.
63func (k *KBPKIClient) Resolve(
64	ctx context.Context, assertion string,
65	offline keybase1.OfflineAvailability) (
66	kbname.NormalizedUsername, keybase1.UserOrTeamID, error) {
67	return k.serviceOwner.KeybaseService().Resolve(ctx, assertion, offline)
68}
69
70// Identify implements the KBPKI interface for KBPKIClient.
71func (k *KBPKIClient) Identify(
72	ctx context.Context, assertion, reason string,
73	offline keybase1.OfflineAvailability) (
74	kbname.NormalizedUsername, keybase1.UserOrTeamID, error) {
75	return k.serviceOwner.KeybaseService().Identify(
76		ctx, assertion, reason, offline)
77}
78
79// NormalizeSocialAssertion implements the KBPKI interface for KBPKIClient.
80func (k *KBPKIClient) NormalizeSocialAssertion(
81	ctx context.Context, assertion string) (keybase1.SocialAssertion, error) {
82	return k.serviceOwner.KeybaseService().NormalizeSocialAssertion(ctx, assertion)
83}
84
85// ResolveImplicitTeam implements the KBPKI interface for KBPKIClient.
86func (k *KBPKIClient) ResolveImplicitTeam(
87	ctx context.Context, assertions, suffix string, tlfType tlf.Type,
88	offline keybase1.OfflineAvailability) (
89	idutil.ImplicitTeamInfo, error) {
90	return k.serviceOwner.KeybaseService().ResolveIdentifyImplicitTeam(
91		ctx, assertions, suffix, tlfType, false, "", offline)
92}
93
94// ResolveImplicitTeamByID implements the KBPKI interface for KBPKIClient.
95func (k *KBPKIClient) ResolveImplicitTeamByID(
96	ctx context.Context, teamID keybase1.TeamID, tlfType tlf.Type,
97	offline keybase1.OfflineAvailability) (
98	idutil.ImplicitTeamInfo, error) {
99	name, err := k.serviceOwner.KeybaseService().ResolveImplicitTeamByID(
100		ctx, teamID)
101	if err != nil {
102		return idutil.ImplicitTeamInfo{}, err
103	}
104
105	assertions, suffix, err := tlf.SplitExtension(name)
106	if err != nil {
107		return idutil.ImplicitTeamInfo{}, err
108	}
109
110	return k.serviceOwner.KeybaseService().ResolveIdentifyImplicitTeam(
111		ctx, assertions, suffix, tlfType, false, "", offline)
112}
113
114// ResolveTeamTLFID implements the KBPKI interface for KBPKIClient.
115func (k *KBPKIClient) ResolveTeamTLFID(
116	ctx context.Context, teamID keybase1.TeamID,
117	offline keybase1.OfflineAvailability) (tlf.ID, error) {
118	settings, err := k.serviceOwner.KeybaseService().GetTeamSettings(
119		ctx, teamID, offline)
120	if err != nil {
121		return tlf.NullID, err
122	}
123	if settings.TlfID.IsNil() {
124		return tlf.NullID, err
125	}
126	tlfID, err := tlf.ParseID(settings.TlfID.String())
127	if err != nil {
128		return tlf.NullID, err
129	}
130	return tlfID, nil
131}
132
133// IdentifyImplicitTeam identifies (and creates if necessary) the
134// given implicit team.
135func (k *KBPKIClient) IdentifyImplicitTeam(
136	ctx context.Context, assertions, suffix string, tlfType tlf.Type,
137	reason string, offline keybase1.OfflineAvailability) (
138	idutil.ImplicitTeamInfo, error) {
139	return k.serviceOwner.KeybaseService().ResolveIdentifyImplicitTeam(
140		ctx, assertions, suffix, tlfType, true, reason, offline)
141}
142
143// GetNormalizedUsername implements the KBPKI interface for
144// KBPKIClient.
145func (k *KBPKIClient) GetNormalizedUsername(
146	ctx context.Context, id keybase1.UserOrTeamID,
147	offline keybase1.OfflineAvailability) (
148	username kbname.NormalizedUsername, err error) {
149	if k.idToUserCache != nil {
150		tmp, ok := k.idToUserCache.Get(id)
151		if ok {
152			username, ok = tmp.(kbname.NormalizedUsername)
153			if ok {
154				return username, nil
155			}
156		}
157	}
158
159	var assertion string
160	if id.IsUser() {
161		assertion = fmt.Sprintf("uid:%s", id)
162	} else {
163		assertion = fmt.Sprintf("tid:%s", id)
164	}
165	username, _, err = k.Resolve(ctx, assertion, offline)
166	if err != nil {
167		return kbname.NormalizedUsername(""), err
168	}
169	k.idToUserCache.Add(id, username)
170	return username, nil
171}
172
173func (k *KBPKIClient) hasVerifyingKey(
174	ctx context.Context, uid keybase1.UID, verifyingKey kbfscrypto.VerifyingKey,
175	atServerTime time.Time, offline keybase1.OfflineAvailability) (
176	bool, error) {
177	userInfo, err := k.loadUserPlusKeys(ctx, uid, verifyingKey.KID(), offline)
178	if err != nil {
179		return false, err
180	}
181
182	for _, key := range userInfo.VerifyingKeys {
183		if verifyingKey.KID().Equal(key.KID()) {
184			return true, nil
185		}
186	}
187
188	info, ok := userInfo.RevokedVerifyingKeys[verifyingKey]
189	if !ok {
190		return false, nil
191	}
192
193	// We add some slack to the revoke time, because the MD server
194	// won't instanteneously find out about the revoke -- it might
195	// keep accepting writes from the revoked device for a short
196	// period of time until it learns about the revoke.
197	const revokeSlack = 1 * time.Minute
198	revokedTime := keybase1.FromTime(info.Time)
199	// Check the server times -- if the key was valid at the given
200	// time, the caller can proceed with their merkle checking if
201	// desired.
202	if atServerTime.Before(revokedTime.Add(revokeSlack)) {
203		k.log.CDebugf(ctx, "Revoked verifying key %s for user %s passes time "+
204			"check (revoked time: %v vs. server time %v, slack=%s)",
205			verifyingKey.KID(), uid, revokedTime, atServerTime, revokeSlack)
206		return false, RevokedDeviceVerificationError{info}
207	}
208	k.log.CDebugf(ctx, "Not trusting revoked verifying key %s for "+
209		"user %s (revoked time: %v vs. server time %v, slack=%s)",
210		verifyingKey.KID(), uid, revokedTime, atServerTime, revokeSlack)
211	return false, nil
212}
213
214// HasVerifyingKey implements the KBPKI interface for KBPKIClient.
215func (k *KBPKIClient) HasVerifyingKey(
216	ctx context.Context, uid keybase1.UID, verifyingKey kbfscrypto.VerifyingKey,
217	atServerTime time.Time, offline keybase1.OfflineAvailability) error {
218	ok, err := k.hasVerifyingKey(ctx, uid, verifyingKey, atServerTime, offline)
219	if err != nil {
220		return err
221	}
222	if ok {
223		return nil
224	}
225
226	// If the first attempt couldn't find the key, try again after
227	// clearing our local cache.  We might have stale info if the
228	// service hasn't learned of the users' new key yet.
229	k.serviceOwner.KeybaseService().FlushUserFromLocalCache(ctx, uid)
230
231	ok, err = k.hasVerifyingKey(ctx, uid, verifyingKey, atServerTime, offline)
232	if err != nil {
233		return err
234	}
235	if !ok {
236		return VerifyingKeyNotFoundError{verifyingKey}
237	}
238	return nil
239}
240
241func (k *KBPKIClient) loadUserPlusKeys(ctx context.Context,
242	uid keybase1.UID, pollForKID keybase1.KID,
243	offline keybase1.OfflineAvailability) (idutil.UserInfo, error) {
244	return k.serviceOwner.KeybaseService().LoadUserPlusKeys(
245		ctx, uid, pollForKID, offline)
246}
247
248// GetCryptPublicKeys implements the KBPKI interface for KBPKIClient.
249func (k *KBPKIClient) GetCryptPublicKeys(
250	ctx context.Context, uid keybase1.UID,
251	offline keybase1.OfflineAvailability) (
252	keys []kbfscrypto.CryptPublicKey, err error) {
253	userInfo, err := k.loadUserPlusKeys(ctx, uid, "", offline)
254	if err != nil {
255		return nil, err
256	}
257	return userInfo.CryptPublicKeys, nil
258}
259
260// GetTeamTLFCryptKeys implements the KBPKI interface for KBPKIClient.
261func (k *KBPKIClient) GetTeamTLFCryptKeys(
262	ctx context.Context, tid keybase1.TeamID, desiredKeyGen kbfsmd.KeyGen,
263	offline keybase1.OfflineAvailability) (
264	map[kbfsmd.KeyGen]kbfscrypto.TLFCryptKey, kbfsmd.KeyGen, error) {
265	teamInfo, err := k.serviceOwner.KeybaseService().LoadTeamPlusKeys(
266		ctx, tid, tlf.Unknown, desiredKeyGen, keybase1.UserVersion{},
267		kbfscrypto.VerifyingKey{}, keybase1.TeamRole_NONE, offline)
268	if err != nil {
269		return nil, 0, err
270	}
271	return teamInfo.CryptKeys, teamInfo.LatestKeyGen, nil
272}
273
274// GetCurrentMerkleRoot implements the KBPKI interface for KBPKIClient.
275func (k *KBPKIClient) GetCurrentMerkleRoot(ctx context.Context) (
276	keybase1.MerkleRootV2, time.Time, error) {
277	return k.serviceOwner.KeybaseService().GetCurrentMerkleRoot(ctx)
278}
279
280// VerifyMerkleRoot implements the KBPKI interface for KBPKIClient.
281func (k *KBPKIClient) VerifyMerkleRoot(
282	ctx context.Context, root keybase1.MerkleRootV2,
283	kbfsRoot keybase1.KBFSRoot) error {
284	return k.serviceOwner.KeybaseService().VerifyMerkleRoot(
285		ctx, root, kbfsRoot)
286}
287
288// IsTeamWriter implements the KBPKI interface for KBPKIClient.
289func (k *KBPKIClient) IsTeamWriter(
290	ctx context.Context, tid keybase1.TeamID, uid keybase1.UID,
291	verifyingKey kbfscrypto.VerifyingKey,
292	offline keybase1.OfflineAvailability) (bool, error) {
293	if uid.IsNil() || verifyingKey.IsNil() {
294		// A sessionless user can never be a writer.
295		return false, nil
296	}
297
298	// Use the verifying key to find out the eldest seqno of the user.
299	userInfo, err := k.loadUserPlusKeys(ctx, uid, verifyingKey.KID(), offline)
300	if err != nil {
301		return false, err
302	}
303
304	found := false
305	for _, key := range userInfo.VerifyingKeys {
306		if verifyingKey.KID().Equal(key.KID()) {
307			found = true
308			break
309		}
310	}
311	if !found {
312		// For the purposes of finding the eldest seqno, we need to
313		// check the verified key against the list of revoked keys as
314		// well.  (The caller should use `HasVerifyingKey` later to
315		// check whether the revoked key was valid at the time of the
316		// update or not.)
317		_, found = userInfo.RevokedVerifyingKeys[verifyingKey]
318	}
319	if !found {
320		// The user doesn't currently have this KID, therefore they
321		// shouldn't be treated as a writer.  The caller should check
322		// historical device records and team membership.
323		k.log.CDebugf(ctx, "User %s doesn't currently have verifying key %s",
324			uid, verifyingKey.KID())
325		return false, nil
326	}
327
328	desiredUser := keybase1.UserVersion{
329		Uid:         uid,
330		EldestSeqno: userInfo.EldestSeqno,
331	}
332	teamInfo, err := k.serviceOwner.KeybaseService().LoadTeamPlusKeys(
333		ctx, tid, tlf.Unknown, kbfsmd.UnspecifiedKeyGen, desiredUser,
334		kbfscrypto.VerifyingKey{}, keybase1.TeamRole_WRITER, offline)
335	if err != nil {
336		if tid.IsPublic() {
337			if _, notFound := err.(libkb.NotFoundError); notFound {
338				// We are probably just not a writer of this public team.
339				k.log.CDebugf(ctx,
340					"Ignoring not found error for public team: %+v", err)
341				return false, nil
342			}
343		}
344		return false, err
345	}
346	return teamInfo.Writers[uid], nil
347}
348
349// NoLongerTeamWriter implements the KBPKI interface for KBPKIClient.
350func (k *KBPKIClient) NoLongerTeamWriter(
351	ctx context.Context, tid keybase1.TeamID, tlfType tlf.Type,
352	uid keybase1.UID, verifyingKey kbfscrypto.VerifyingKey,
353	offline keybase1.OfflineAvailability) (keybase1.MerkleRootV2, error) {
354	if uid.IsNil() || verifyingKey.IsNil() {
355		// A sessionless user can never be a writer.
356		return keybase1.MerkleRootV2{}, nil
357	}
358
359	// We don't need the eldest seqno when we look up an older writer,
360	// the service takes care of that for us.
361	desiredUser := keybase1.UserVersion{
362		Uid: uid,
363	}
364
365	teamInfo, err := k.serviceOwner.KeybaseService().LoadTeamPlusKeys(
366		ctx, tid, tlfType, kbfsmd.UnspecifiedKeyGen, desiredUser,
367		verifyingKey, keybase1.TeamRole_WRITER, offline)
368	if err != nil {
369		return keybase1.MerkleRootV2{}, err
370	}
371	return teamInfo.LastWriters[verifyingKey], nil
372}
373
374// IsTeamReader implements the KBPKI interface for KBPKIClient.
375func (k *KBPKIClient) IsTeamReader(
376	ctx context.Context, tid keybase1.TeamID, uid keybase1.UID,
377	offline keybase1.OfflineAvailability) (bool, error) {
378	desiredUser := keybase1.UserVersion{Uid: uid}
379	teamInfo, err := k.serviceOwner.KeybaseService().LoadTeamPlusKeys(
380		ctx, tid, tlf.Unknown, kbfsmd.UnspecifiedKeyGen, desiredUser,
381		kbfscrypto.VerifyingKey{}, keybase1.TeamRole_READER, offline)
382	if err != nil {
383		return false, err
384	}
385	return tid.IsPublic() || teamInfo.Writers[uid] || teamInfo.Readers[uid], nil
386}
387
388// GetTeamRootID implements the KBPKI interface for KBPKIClient.
389func (k *KBPKIClient) GetTeamRootID(
390	ctx context.Context, tid keybase1.TeamID,
391	offline keybase1.OfflineAvailability) (keybase1.TeamID, error) {
392	if !tid.IsSubTeam() {
393		return tid, nil
394	}
395
396	teamInfo, err := k.serviceOwner.KeybaseService().LoadTeamPlusKeys(
397		ctx, tid, tlf.Unknown, kbfsmd.UnspecifiedKeyGen, keybase1.UserVersion{},
398		kbfscrypto.VerifyingKey{}, keybase1.TeamRole_NONE, offline)
399	if err != nil {
400		return keybase1.TeamID(""), err
401	}
402	return teamInfo.RootID, nil
403}
404
405// CreateTeamTLF implements the KBPKI interface for KBPKIClient.
406func (k *KBPKIClient) CreateTeamTLF(
407	ctx context.Context, teamID keybase1.TeamID, tlfID tlf.ID) error {
408	return k.serviceOwner.KeybaseService().CreateTeamTLF(ctx, teamID, tlfID)
409}
410
411// FavoriteAdd implements the KBPKI interface for KBPKIClient.
412func (k *KBPKIClient) FavoriteAdd(ctx context.Context, folder keybase1.FolderHandle) error {
413	return k.serviceOwner.KeybaseService().FavoriteAdd(ctx, folder)
414}
415
416// FavoriteDelete implements the KBPKI interface for KBPKIClient.
417func (k *KBPKIClient) FavoriteDelete(ctx context.Context, folder keybase1.FolderHandle) error {
418	return k.serviceOwner.KeybaseService().FavoriteDelete(ctx, folder)
419}
420
421// FavoriteList implements the KBPKI interface for KBPKIClient.
422func (k *KBPKIClient) FavoriteList(ctx context.Context) (
423	keybase1.FavoritesResult, error) {
424	const sessionID = 0
425	return k.serviceOwner.KeybaseService().FavoriteList(ctx, sessionID)
426}
427
428// Notify implements the KBPKI interface for KBPKIClient.
429func (k *KBPKIClient) Notify(ctx context.Context, notification *keybase1.FSNotification) error {
430	return k.serviceOwner.KeybaseService().Notify(ctx, notification)
431}
432
433// NotifyPathUpdated implements the KBPKI interface for KBPKIClient.
434func (k *KBPKIClient) NotifyPathUpdated(
435	ctx context.Context, path string) error {
436	return k.serviceOwner.KeybaseService().NotifyPathUpdated(ctx, path)
437}
438
439// PutGitMetadata implements the KBPKI interface for KBPKIClient.
440func (k *KBPKIClient) PutGitMetadata(
441	ctx context.Context, folder keybase1.FolderHandle, repoID keybase1.RepoID,
442	metadata keybase1.GitLocalMetadata) error {
443	return k.serviceOwner.KeybaseService().PutGitMetadata(
444		ctx, folder, repoID, metadata)
445}
446
447// InvalidateTeamCacheForID implements the KBPKI interface for KBPKIClient.
448func (k *KBPKIClient) InvalidateTeamCacheForID(tid keybase1.TeamID) {
449	k.idToUserCache.Remove(tid.AsUserOrTeam())
450}
451