1package saltpackkeys
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/keybase/client/go/libkb"
8	"github.com/keybase/client/go/protocol/keybase1"
9	"github.com/keybase/client/go/teams"
10	"github.com/keybase/go-framed-msgpack-rpc/rpc"
11	"github.com/keybase/saltpack"
12)
13
14// KeyPseudonymResolver resolves new (team based) Key Pseudonyms, but falls back to old Kbfs Pseudonyms when it cannot find any match.
15// A mock implementation (which does not communicate with the sever and avoids circular dependencies) is available in the saltpackkeysmocks package.
16type KeyPseudonymResolver struct {
17	m libkb.MetaContext
18}
19
20var _ saltpack.SymmetricKeyResolver = (*KeyPseudonymResolver)(nil)
21
22func NewKeyPseudonymResolver(m libkb.MetaContext) saltpack.SymmetricKeyResolver {
23	return &KeyPseudonymResolver{m: m}
24}
25
26func (r *KeyPseudonymResolver) ResolveKeys(identifiers [][]byte) ([]*saltpack.SymmetricKey, error) {
27	keyPseudonyms := []libkb.KeyPseudonym{}
28	for _, identifier := range identifiers {
29		pseudonym := libkb.KeyPseudonym{}
30		if len(pseudonym) != len(identifier) {
31			return nil, fmt.Errorf("identifier is the wrong length for a key pseudonym (%d != %d)", len(pseudonym), len(identifier))
32		}
33		copy(pseudonym[:], identifier)
34		keyPseudonyms = append(keyPseudonyms, pseudonym)
35	}
36
37	results, err := libkb.GetKeyPseudonyms(r.m, keyPseudonyms)
38	if err != nil {
39		return nil, err
40	}
41
42	success := false
43
44	symmetricKeys := []*saltpack.SymmetricKey{}
45	for _, result := range results {
46		if result.Err != nil || result.Info.Application != keybase1.TeamApplication_SALTPACK {
47			r.m.Debug("skipping unresolved pseudonym: %s", result.Err)
48			symmetricKeys = append(symmetricKeys, nil)
49			continue
50		}
51		r.m.Debug("resolved pseudonym for %s, fetching key", result.Info.ID)
52		symmetricKey, err := r.getSymmetricKey(result.Info.ID, result.Info.KeyGen)
53		if err != nil {
54			return nil, err
55		}
56		success = true
57		symmetricKeys = append(symmetricKeys, symmetricKey)
58	}
59
60	if success {
61		return symmetricKeys, nil
62	}
63	r.m.Debug("No pseudonyms resolved, fallback to old kbfs pseudonyms")
64	// Fallback to old kbfs pseudonyms
65	return r.kbfsResolveKeys(identifiers)
66}
67
68func (r *KeyPseudonymResolver) getSymmetricKey(id keybase1.UserOrTeamID, gen libkb.KeyGen) (*saltpack.SymmetricKey, error) {
69	// For now resolving key pseudonyms for users is not necessary, as keybase encrypt does not
70	// use symmetric per user encryption keys.
71
72	team, err := teams.Load(r.m.Ctx(), r.m.G(), keybase1.LoadTeamArg{
73		ID: keybase1.TeamID(id),
74	})
75	if err != nil {
76		return nil, err
77	}
78
79	var key keybase1.TeamApplicationKey
80	key, err = team.SaltpackEncryptionKeyAtGeneration(r.m.Ctx(), keybase1.PerTeamKeyGeneration(gen))
81	if err != nil {
82		return nil, err
83	}
84
85	ssk := saltpack.SymmetricKey(key.Key)
86	return &ssk, nil
87}
88
89func (r *KeyPseudonymResolver) kbfsResolveKeys(identifiers [][]byte) ([]*saltpack.SymmetricKey, error) {
90	tlfPseudonyms := []libkb.TlfPseudonym{}
91	for _, identifier := range identifiers {
92		pseudonym := libkb.TlfPseudonym{}
93		if len(pseudonym) != len(identifier) {
94			return nil, fmt.Errorf("identifier is the wrong length for a TLF pseudonym (%d != %d)", len(pseudonym), len(identifier))
95		}
96		copy(pseudonym[:], identifier)
97		tlfPseudonyms = append(tlfPseudonyms, pseudonym)
98	}
99
100	results, err := libkb.GetTlfPseudonyms(r.m.Ctx(), r.m.G(), tlfPseudonyms)
101	if err != nil {
102		return nil, err
103	}
104
105	symmetricKeys := []*saltpack.SymmetricKey{}
106	for _, result := range results {
107		if result.Err != nil {
108			r.m.Debug("skipping unresolved pseudonym: %s", result.Err)
109			symmetricKeys = append(symmetricKeys, nil)
110			continue
111		}
112		r.m.Debug("resolved pseudonym for %s, fetching key", result.Info.Name)
113		symmetricKey, err := r.kbfsGetSymmetricKey(r.m, *result.Info)
114		if err != nil {
115			return nil, err
116		}
117		symmetricKeys = append(symmetricKeys, symmetricKey)
118	}
119	return symmetricKeys, nil
120}
121
122func (r *KeyPseudonymResolver) getCryptKeys(m libkb.MetaContext, name string) (keybase1.GetTLFCryptKeysRes, error) {
123	xp := m.G().ConnectionManager.LookupByClientType(keybase1.ClientType_KBFS)
124	if xp == nil {
125		return keybase1.GetTLFCryptKeysRes{}, libkb.KBFSNotRunningError{}
126	}
127	cli := &keybase1.TlfKeysClient{
128		Cli: rpc.NewClient(xp, libkb.NewContextifiedErrorUnwrapper(r.m.G()), libkb.LogTagsFromContext),
129	}
130	return cli.GetTLFCryptKeys(m.Ctx(), keybase1.TLFQuery{
131		TlfName:          name,
132		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI,
133	})
134}
135
136func (r *KeyPseudonymResolver) kbfsGetSymmetricKey(m libkb.MetaContext, info libkb.TlfPseudonymServerInfo) (*saltpack.SymmetricKey, error) {
137	// NOTE: In order to handle finalized TLFs (which is one of the main
138	// benefits of using TLF keys to begin with, for forward readability), we
139	// need the server to tell us what the current, potentially-finalized name
140	// of the TLF is. If that's not the same as what the name was when the
141	// message was sent, we can't necessarily check that the server is being
142	// honest. That's ok insofar as we're not relying on these keys for
143	// authenticity, but it's a drag to not be able to use the pseudonym
144	// machinery.
145
146	// TODO: Check as much as we can, if the original TLF was fully resolved.
147
148	// Strip "/keybase/private/" from the name.
149	basename := strings.TrimPrefix(info.UntrustedCurrentName, "/keybase/private/")
150	if len(basename) >= len(info.UntrustedCurrentName) {
151		return nil, fmt.Errorf("unexpected prefix, expected '/keybase/private', found %q", info.UntrustedCurrentName)
152	}
153	res, err := r.getCryptKeys(m, basename)
154	if err != nil {
155		return nil, err
156	}
157	for _, key := range res.CryptKeys {
158		if libkb.KeyGen(key.KeyGeneration) == info.KeyGen {
159			// Success!
160			return (*saltpack.SymmetricKey)(&key.Key), nil
161		}
162	}
163	return nil, fmt.Errorf("no keys in TLF %q matched generation %d", basename, info.KeyGen)
164}
165