1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4//
5// Similar to libkb/kbsigs.go, but for teams sigs.
6//
7package teams
8
9import (
10	"encoding/hex"
11	"errors"
12	"fmt"
13
14	"golang.org/x/net/context"
15
16	"github.com/davecgh/go-spew/spew"
17	"github.com/keybase/client/go/libkb"
18	keybase1 "github.com/keybase/client/go/protocol/keybase1"
19	"github.com/keybase/client/go/sig3"
20	"github.com/keybase/client/go/teams/hidden"
21	jsonw "github.com/keybase/go-jsonw"
22)
23
24// metaContext returns a GlobalContext + a TODO context, since we're not
25// threading through contexts through this library. In the future, we should
26// fix this.
27func metaContext(g *libkb.GlobalContext) libkb.MetaContext {
28	return libkb.NewMetaContextTODO(g)
29}
30
31func TeamRootSig(g *libkb.GlobalContext, me libkb.UserForSignatures, key libkb.GenericKey, teamSection SCTeamSection, merkleRoot libkb.MerkleRoot) (*jsonw.Wrapper, error) {
32	ret, err := libkb.ProofMetadata{
33		SigningUser: me,
34		Eldest:      me.GetEldestKID(),
35		LinkType:    libkb.LinkTypeTeamRoot,
36		SigningKey:  key,
37		Seqno:       1,
38		SigVersion:  libkb.KeybaseSignatureV2,
39		SeqType:     seqTypeForTeamPublicness(teamSection.Public),
40		MerkleRoot:  &merkleRoot,
41	}.ToJSON(metaContext(g))
42	if err != nil {
43		return nil, err
44	}
45
46	teamSectionJSON, err := jsonw.WrapperFromObject(teamSection)
47	if err != nil {
48		return nil, err
49	}
50	err = teamSectionJSON.SetValueAtPath("per_team_key.reverse_sig", jsonw.NewNil())
51	if err != nil {
52		return nil, err
53	}
54
55	body := ret.AtKey("body")
56	err = body.SetKey("team", teamSectionJSON)
57	if err != nil {
58		return nil, err
59	}
60
61	return ret, nil
62}
63
64func NewImplicitTeamName() (res keybase1.TeamName, err error) {
65	dat, err := libkb.RandBytes(keybase1.ImplicitSuffixLengthBytes)
66	if err != nil {
67		return res, err
68	}
69	res, err = keybase1.TeamNameFromString(fmt.Sprintf("%s%s", keybase1.ImplicitTeamPrefix, hex.EncodeToString(dat)))
70	return res, err
71}
72
73func NewSubteamSig(mctx libkb.MetaContext, me libkb.UserForSignatures, key libkb.GenericKey, parentTeam *TeamSigChainState, subteamName keybase1.TeamName, subteamID keybase1.TeamID, admin *SCTeamAdmin) (*jsonw.Wrapper, *hidden.Ratchet, error) {
74	g := mctx.G()
75	prevLinkID, err := libkb.ImportLinkID(parentTeam.GetLatestLinkID())
76	if err != nil {
77		return nil, nil, err
78	}
79	ret, err := libkb.ProofMetadata{
80		SigningUser: me,
81		Eldest:      me.GetEldestKID(),
82		LinkType:    libkb.LinkTypeNewSubteam,
83		SigningKey:  key,
84		SigVersion:  libkb.KeybaseSignatureV2,
85		SeqType:     seqTypeForTeamPublicness(parentTeam.IsPublic()), // children are as public as their parent
86		Seqno:       parentTeam.GetLatestSeqno() + 1,
87		PrevLinkID:  prevLinkID,
88	}.ToJSON(metaContext(g))
89	if err != nil {
90		return nil, nil, err
91	}
92
93	entropy, err := makeSCTeamEntropy()
94	if err != nil {
95		return nil, nil, err
96	}
97
98	ratchet, err := parentTeam.makeHiddenRatchet(mctx)
99	if err != nil {
100		return nil, nil, err
101	}
102
103	teamSection := SCTeamSection{
104		ID: (SCTeamID)(parentTeam.GetID()),
105		Subteam: &SCSubteam{
106			ID:   (SCTeamID)(subteamID),
107			Name: (SCTeamName)(subteamName.String()),
108		},
109		Admin:    admin,
110		Entropy:  entropy,
111		Ratchets: ratchet.ToTeamSection(),
112	}
113	teamSectionJSON, err := jsonw.WrapperFromObject(teamSection)
114	if err != nil {
115		return nil, nil, err
116	}
117	err = ret.SetValueAtPath("body.team", teamSectionJSON)
118	if err != nil {
119		return nil, nil, err
120	}
121
122	return ret, ratchet, nil
123}
124
125func SubteamHeadSig(g *libkb.GlobalContext, me libkb.UserForSignatures, key libkb.GenericKey, subteamTeamSection SCTeamSection, merkleRoot libkb.MerkleRoot) (*jsonw.Wrapper, error) {
126	ret, err := libkb.ProofMetadata{
127		SigningUser: me,
128		Eldest:      me.GetEldestKID(),
129		LinkType:    libkb.LinkTypeSubteamHead,
130		SigningKey:  key,
131		Seqno:       1,
132		SigVersion:  libkb.KeybaseSignatureV2,
133		SeqType:     seqTypeForTeamPublicness(subteamTeamSection.Public),
134		MerkleRoot:  &merkleRoot,
135	}.ToJSON(metaContext(g))
136	if err != nil {
137		return nil, err
138	}
139
140	// Note that the team section here is expected to have its Parent
141	// subsection filled out by the caller, unlike TeamRootSig.
142	teamSectionJSON, err := jsonw.WrapperFromObject(subteamTeamSection)
143	if err != nil {
144		return nil, err
145	}
146	err = teamSectionJSON.SetValueAtPath("per_team_key.reverse_sig", jsonw.NewNil())
147	if err != nil {
148		return nil, err
149	}
150
151	body := ret.AtKey("body")
152	err = body.SetKey("team", teamSectionJSON)
153	if err != nil {
154		return nil, err
155	}
156
157	return ret, nil
158}
159
160func RenameSubteamSig(g *libkb.GlobalContext, me libkb.UserForSignatures, key libkb.GenericKey, parentTeam *TeamSigChainState, teamSection SCTeamSection) (*jsonw.Wrapper, error) {
161	prev, err := parentTeam.GetLatestLibkbLinkID()
162	if err != nil {
163		return nil, err
164	}
165	ret, err := libkb.ProofMetadata{
166		SigningUser: me,
167		Eldest:      me.GetEldestKID(),
168		LinkType:    libkb.LinkTypeRenameSubteam,
169		SigningKey:  key,
170		Seqno:       parentTeam.GetLatestSeqno() + 1,
171		PrevLinkID:  prev,
172		SigVersion:  libkb.KeybaseSignatureV2,
173		SeqType:     seqTypeForTeamPublicness(teamSection.Public),
174	}.ToJSON(metaContext(g))
175	if err != nil {
176		return nil, err
177	}
178
179	teamSectionJSON, err := jsonw.WrapperFromObject(teamSection)
180	if err != nil {
181		return nil, err
182	}
183
184	body := ret.AtKey("body")
185	err = body.SetKey("team", teamSectionJSON)
186	if err != nil {
187		return nil, err
188	}
189
190	return ret, nil
191}
192
193func RenameUpPointerSig(g *libkb.GlobalContext, me libkb.UserForSignatures, key libkb.GenericKey, subteam *TeamSigChainState, teamSection SCTeamSection) (*jsonw.Wrapper, error) {
194	prev, err := subteam.GetLatestLibkbLinkID()
195	if err != nil {
196		return nil, err
197	}
198	ret, err := libkb.ProofMetadata{
199		SigningUser: me,
200		Eldest:      me.GetEldestKID(),
201		LinkType:    libkb.LinkTypeRenameUpPointer,
202		SigningKey:  key,
203		Seqno:       subteam.GetLatestSeqno() + 1,
204		PrevLinkID:  prev,
205		SigVersion:  libkb.KeybaseSignatureV2,
206		SeqType:     seqTypeForTeamPublicness(teamSection.Public),
207	}.ToJSON(metaContext(g))
208	if err != nil {
209		return nil, err
210	}
211
212	teamSectionJSON, err := jsonw.WrapperFromObject(teamSection)
213	if err != nil {
214		return nil, err
215	}
216
217	body := ret.AtKey("body")
218	err = body.SetKey("team", teamSectionJSON)
219	if err != nil {
220		return nil, err
221	}
222
223	return ret, nil
224}
225
226// 15 random bytes, followed by the byte 0x25, encoded as hex
227func NewSubteamID(public bool) keybase1.TeamID {
228	var useSuffix byte = keybase1.SUB_TEAMID_PRIVATE_SUFFIX
229	if public {
230		useSuffix = keybase1.SUB_TEAMID_PUBLIC_SUFFIX
231	}
232	idBytes, err := libkb.RandBytesWithSuffix(16, useSuffix)
233	if err != nil {
234		panic("RandBytes failed: " + err.Error())
235	}
236	return keybase1.TeamID(hex.EncodeToString(idBytes))
237}
238
239func NewInviteID() SCTeamInviteID {
240	b, err := libkb.RandBytesWithSuffix(16, libkb.InviteIDTag)
241	if err != nil {
242		panic("RandBytes failed: " + err.Error())
243	}
244	return SCTeamInviteID(hex.EncodeToString(b))
245}
246
247func ChangeSig(g *libkb.GlobalContext, me libkb.UserForSignatures, prev libkb.LinkID, seqno keybase1.Seqno, key libkb.GenericKey, teamSection SCTeamSection,
248	linkType libkb.LinkType, merkleRoot *libkb.MerkleRoot) (*jsonw.Wrapper, error) {
249	if teamSection.PerTeamKey != nil {
250		if teamSection.PerTeamKey.ReverseSig != "" {
251			return nil, errors.New("ChangeMembershipSig called with PerTeamKey.ReverseSig already set")
252		}
253	}
254
255	ret, err := libkb.ProofMetadata{
256		LinkType:    linkType,
257		SigningUser: me,
258		Eldest:      me.GetEldestKID(),
259		SigningKey:  key,
260		Seqno:       seqno,
261		PrevLinkID:  prev,
262		SigVersion:  libkb.KeybaseSignatureV2,
263		SeqType:     seqTypeForTeamPublicness(teamSection.Public),
264		MerkleRoot:  merkleRoot,
265	}.ToJSON(metaContext(g))
266	if err != nil {
267		return nil, err
268	}
269
270	teamSectionJSON, err := jsonw.WrapperFromObject(teamSection)
271	if err != nil {
272		return nil, err
273	}
274
275	body := ret.AtKey("body")
276	err = body.SetKey("team", teamSectionJSON)
277	if err != nil {
278		return nil, err
279	}
280
281	return ret, nil
282}
283
284func makeSCTeamEntropy() (SCTeamEntropy, error) {
285	entropy, err := libkb.LinkEntropy()
286	if err != nil {
287		return SCTeamEntropy(""), err
288	}
289	return SCTeamEntropy(entropy), nil
290}
291
292func seqTypeForTeamPublicness(public bool) keybase1.SeqType {
293	if public {
294		return keybase1.SeqType_PUBLIC
295	}
296	return keybase1.SeqType_SEMIPRIVATE
297}
298
299func precheckLinkToPost(ctx context.Context, g *libkb.GlobalContext,
300	sigMultiItem libkb.SigMultiItem, state *TeamSigChainState,
301	me keybase1.UserVersion) (err error) {
302	return precheckLinksToPost(ctx, g, []libkb.SigMultiItem{sigMultiItem}, state, me)
303}
304
305func appendChainLinkSig3(ctx context.Context, g *libkb.GlobalContext,
306	sig libkb.Sig3, state *TeamSigChainState,
307	me keybase1.UserVersion) (err error) {
308
309	mctx := libkb.NewMetaContext(ctx, g)
310
311	if len(sig.Outer) == 0 || len(sig.Sig) == 0 {
312		return NewPrecheckStructuralError("got a stubbed v3 link on post, which isn't allowed", nil)
313	}
314
315	hp := hidden.NewLoaderPackageForPrecheck(mctx, state.GetID(), state.hidden)
316	ex := sig3.ExportJSON{
317		Inner: sig.Inner,
318		Outer: sig.Outer,
319		Sig:   sig.Sig,
320	}
321	err = hp.Update(mctx, []sig3.ExportJSON{ex}, keybase1.Seqno(0))
322	if err != nil {
323		return err
324	}
325	mctx.Debug("appendChainLinkSig3 success for %s", sig.Outer)
326	return nil
327}
328
329func precheckLinksToPost(ctx context.Context, g *libkb.GlobalContext,
330	sigMultiItems []libkb.SigMultiItem, state *TeamSigChainState,
331	me keybase1.UserVersion) (err error) {
332	_, err = precheckLinksToState(ctx, g, sigMultiItems, state, me)
333	return err
334}
335
336func precheckLinksToState(ctx context.Context, g *libkb.GlobalContext,
337	sigMultiItems []libkb.SigMultiItem, state *TeamSigChainState,
338	me keybase1.UserVersion) (newState *TeamSigChainState, err error) {
339
340	defer g.CTrace(ctx, "precheckLinksToState", &err)()
341
342	isAdmin := true
343	if state != nil {
344		role, err := state.GetUserRole(me)
345		if err != nil {
346			role = keybase1.TeamRole_NONE
347		}
348		isAdmin = role.IsAdminOrAbove()
349
350		// As an optimization, AppendChainLink consumes its state.
351		// We don't consume our state parameter.
352		// So clone state before we pass it along to be consumed.
353		state = state.DeepCopyToPtr()
354	}
355
356	signer := SignerX{
357		signer:        me,
358		implicitAdmin: !isAdmin,
359	}
360
361	for i, sigItem := range sigMultiItems {
362
363		if sigItem.Sig3 != nil {
364			err = appendChainLinkSig3(ctx, g, *sigItem.Sig3, state, me)
365			if err != nil {
366				g.Log.CDebugf(ctx, "precheckLinksToState: link (sig3) %v/%v rejected: %v", i+1, len(sigMultiItems), err)
367				return nil, NewPrecheckAppendError(err)
368			}
369			continue
370		}
371
372		outerLink, err := libkb.DecodeOuterLinkV2(sigItem.Sig)
373		if err != nil {
374			return nil, NewPrecheckStructuralError("unpack outer", err)
375		}
376
377		link1 := SCChainLink{
378			Seqno:   outerLink.Seqno,
379			Sig:     sigItem.Sig,
380			Payload: sigItem.SigInner,
381			UID:     me.Uid,
382			Version: 2,
383		}
384		link2, err := unpackChainLink(&link1)
385		if err != nil {
386			return nil, NewPrecheckStructuralError("unpack link", err)
387		}
388
389		if link2.isStubbed() {
390			return nil, NewPrecheckStructuralError("link missing inner", nil)
391		}
392
393		newState, err := AppendChainLink(ctx, g, me, state, link2, &signer)
394		if err != nil {
395			if link2.inner != nil && link2.inner.Body.Team != nil && link2.inner.Body.Team.Members != nil {
396				g.Log.CDebugf(ctx, "precheckLinksToState: link %v/%v rejected: %v", i+1, len(sigMultiItems), spew.Sprintf("%v", *link2.inner.Body.Team.Members))
397			} else {
398				g.Log.CDebugf(ctx, "precheckLinksToState: link %v/%v rejected", i+1, len(sigMultiItems))
399			}
400			return nil, NewPrecheckAppendError(err)
401		}
402		state = &newState
403	}
404
405	return state, nil
406}
407