/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/teams/create.go
693 строки
20 KB
chrisnojima-zoom
Version 670 - clean2 (#29122)
08 июн 2026, 19:31
Не верифицирован
08 июн 2026, 19:31
1943197
Код
Авторство
О чём код?
package teams import ( "context" "errors" "fmt" "github.com/keybase/client/go/engine" "github.com/keybase/client/go/libkb" "github.com/keybase/client/go/protocol/keybase1" "github.com/keybase/client/go/teams/hidden" jsonw "github.com/keybase/go-jsonw" ) func CreateImplicitTeam(ctx context.Context, g *libkb.GlobalContext, impTeam keybase1.ImplicitTeamDisplayName) (res keybase1.TeamID, teamName keybase1.TeamName, err error) { defer g.CTrace(ctx, "CreateImplicitTeam", &err)() teamName, err = NewImplicitTeamName() if err != nil { return res, teamName, err } teamID := teamName.ToTeamID(impTeam.IsPublic) merkleRoot, err := g.MerkleClient.FetchRootFromServer(libkb.NewMetaContext(ctx, g), libkb.TeamMerkleFreshnessForAdmin) if err != nil { return res, teamName, err } perUserKeyUpgradeSoft(ctx, g, "create-implicit-team") me, err := loadMeForSignatures(ctx, g) if err != nil { return res, teamName, err } // Load all the Keybase users loadUsernameList := func(usernames []string) (res []*keybase1.UserPlusKeysV2, err error) { for _, username := range usernames { arg := libkb.NewLoadUserArg(g).WithName(username).WithNetContext(ctx).WithPublicKeyOptional() upak, _, err := g.GetUPAKLoader().LoadV2(arg) if err != nil { g.Log.CDebugf(ctx, "CreateImplicitTeam: failed to load user: %s msg: %s", username, err) return res, err } res = append(res, &upak.Current) } return res, nil } ownerUPAKs, err := loadUsernameList(impTeam.Writers.KeybaseUsers) if err != nil { return res, teamName, err } readerUPAKs, err := loadUsernameList(impTeam.Readers.KeybaseUsers) if err != nil { return res, teamName, err } var owners []SCTeamMember var readers []SCTeamMember var ownerInvites []SCTeamInvite var readerInvites []SCTeamInvite // Form secret boxes and make invites for KB users with no PUKs secretboxRecipients := make(map[keybase1.UserVersion]keybase1.PerUserKey) // Form secret boxes for KB users with PUKs, and invites for those without for _, upak := range ownerUPAKs { uv := upak.ToUserVersion() puk := upak.GetLatestPerUserKey() if puk == nil { // Add this person as an invite if they do not have a puk ownerInvites = append(ownerInvites, SCTeamInvite{ Type: "keybase", Name: uv.TeamInviteName(), ID: NewInviteID(), }) } else { secretboxRecipients[uv] = *puk owners = append(owners, SCTeamMember(uv)) } } for _, upak := range readerUPAKs { uv := upak.ToUserVersion() puk := upak.GetLatestPerUserKey() if puk == nil { // Add this person as an invite if they do not have a puk readerInvites = append(readerInvites, SCTeamInvite{ Type: "keybase", Name: uv.TeamInviteName(), ID: NewInviteID(), }) } else { secretboxRecipients[uv] = *puk readers = append(readers, SCTeamMember(uv)) } } members := SCTeamMembers{ Owners: &[]SCTeamMember{}, Admins: &[]SCTeamMember{}, Writers: &[]SCTeamMember{}, Readers: &[]SCTeamMember{}, Bots: &[]SCTeamMember{}, RestrictedBots: &[]SCTeamMember{}, } if len(owners) > 0 { members.Owners = &owners } if len(readers) > 0 { members.Readers = &readers } // Add invites for assertions for _, assertion := range impTeam.Writers.UnresolvedUsers { ownerInvites = append(ownerInvites, SCTeamInvite{ Type: assertion.TeamInviteType(), Name: assertion.TeamInviteName(), ID: NewInviteID(), }) } for _, assertion := range impTeam.Readers.UnresolvedUsers { readerInvites = append(readerInvites, SCTeamInvite{ Type: assertion.TeamInviteType(), Name: assertion.TeamInviteName(), ID: NewInviteID(), }) } invites := &SCTeamInvites{ Owners: nil, Admins: nil, Writers: nil, Readers: nil, } if len(ownerInvites) > 0 { invites.Owners = &ownerInvites } if len(readerInvites) > 0 { invites.Readers = &readerInvites } // Post the team return teamID, teamName, makeSigAndPostRootTeam(ctx, g, me, members, invites, secretboxRecipients, teamName.String(), teamID, impTeam.IsPublic, true, nil, *merkleRoot) } func makeSigAndPostRootTeam(ctx context.Context, g *libkb.GlobalContext, me libkb.UserForSignatures, members SCTeamMembers, invites *SCTeamInvites, secretboxRecipients map[keybase1.UserVersion]keybase1.PerUserKey, name string, teamID keybase1.TeamID, public, implicit bool, settings *SCTeamSettings, merkleRoot libkb.MerkleRoot, ) (err error) { mctx := libkb.NewMetaContext(ctx, g) defer g.Trace("makeSigAndPostRootTeam", &err)() mctx.Debug("makeSigAndPostRootTeam get device keys") deviceSigningKey, err := mctx.G().ActiveDevice.SigningKey() if err != nil { return err } deviceEncryptionKey, err := mctx.G().ActiveDevice.EncryptionKey() if err != nil { return err } // These boxes will get posted along with the sig below. m, err := NewTeamKeyManager(mctx.G(), teamID) if err != nil { return err } secretboxes, err := m.SharedSecretBoxes(mctx, deviceEncryptionKey, secretboxRecipients) if err != nil { return err } perTeamSigningKey, err := m.SigningKey() if err != nil { return err } perTeamEncryptionKey, err := m.EncryptionKey() if err != nil { return err } mctx.Debug("makeSigAndPostRootTeam make sigs") teamSection, err := makeRootTeamSection(name, teamID, members, invites, perTeamSigningKey.GetKID(), perTeamEncryptionKey.GetKID(), public, implicit, settings) if err != nil { return err } err = addSummaryHash(&teamSection, secretboxes) if err != nil { return err } // At this point the team section has every field filled out except the // reverse sig. Now we'll wrap it into a full sig, marshal it to JSON, and // sign it, *twice*. The first time with the per-team signing key, to // produce the reverse sig, and the second time with the device signing // key, after the reverse sig has been written in. sigBodyBeforeReverse, err := TeamRootSig(g, me, deviceSigningKey, teamSection, merkleRoot) if err != nil { return err } // Note that this (sigchain-v1-style) reverse sig is made with the derived *per-team* signing key. reverseSig, _, _, err := libkb.SignJSON(sigBodyBeforeReverse, perTeamSigningKey) if err != nil { return err } // Update the team section to include the reverse sig, sign it again, and // make a sigchain-v2-style sig out of it. Doing it this way, instead of // generating it twice with different parameters, makes it less likely to // accidentally capture different global state (like ctime and merkle // seqno). sigBodyAfterReverse := sigBodyBeforeReverse err = sigBodyAfterReverse.SetValueAtPath("body.team.per_team_key.reverse_sig", jsonw.NewString(reverseSig)) if err != nil { return err } sigJSONAfterReverse, err := sigBodyAfterReverse.Marshal() if err != nil { return err } seqType := seqTypeForTeamPublicness(public) v2Sig, _, _, err := libkb.MakeSigchainV2OuterSig( mctx, deviceSigningKey, libkb.LinkTypeTeamRoot, 1, /* seqno */ sigJSONAfterReverse, nil, /* prevLinkID */ libkb.SigHasRevokes(false), seqType, libkb.SigIgnoreIfUnsupported(false), nil, ) if err != nil { return err } sigMultiItem := libkb.SigMultiItem{ Sig: v2Sig, SigningKID: deviceSigningKey.GetKID(), Type: string(libkb.LinkTypeTeamRoot), SeqType: seqType, SigInner: string(sigJSONAfterReverse), TeamID: teamID, PublicKeys: &libkb.SigMultiItemPublicKeys{ Encryption: perTeamEncryptionKey.GetKID(), Signing: perTeamSigningKey.GetKID(), }, Version: libkb.KeybaseSignatureV2, } err = precheckLinkToPost(ctx, g, sigMultiItem, nil, me.ToUserVersion()) if err != nil { mctx.Debug("cannot post link (precheck): %v", err) return err } mctx.Debug("makeSigAndPostRootTeam post sigs") payload := make(libkb.JSONPayload) payload["sigs"] = []any{sigMultiItem} payload["per_team_key"] = secretboxes _, err = mctx.G().API.PostJSON(mctx, libkb.APIArg{ Endpoint: "sig/multi", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, }) if err != nil { return err } mctx.Debug("makeSigAndPostRootTeam created team: %v", teamID) return nil } func CreateRootTeam(ctx context.Context, g *libkb.GlobalContext, nameString string, settings keybase1.TeamSettings) (res *keybase1.TeamID, err error) { defer g.CTrace(ctx, "CreateRootTeam", &err)() perUserKeyUpgradeSoft(ctx, g, "create-root-team") name, err := keybase1.TeamNameFromString(nameString) if err != nil { return nil, err } if !name.IsRootTeam() { return nil, fmt.Errorf("cannot create root team with subteam name: %v", nameString) } merkleRoot, err := g.MerkleClient.FetchRootFromServer(libkb.NewMetaContext(ctx, g), libkb.TeamMerkleFreshnessForAdmin) if err != nil { return nil, err } g.Log.CDebugf(ctx, "CreateRootTeam load me") me, err := loadMeForSignatures(ctx, g) if err != nil { return nil, err } ownerLatest := me.GetLatestPerUserKey() if ownerLatest == nil { return nil, errors.New("can't create a new team without having provisioned a per-user key") } secretboxRecipients := map[keybase1.UserVersion]keybase1.PerUserKey{ me.ToUserVersion(): *ownerLatest, } members := SCTeamMembers{ Owners: &[]SCTeamMember{SCTeamMember(me.ToUserVersion())}, Admins: &[]SCTeamMember{}, Writers: &[]SCTeamMember{}, Readers: &[]SCTeamMember{}, Bots: &[]SCTeamMember{}, RestrictedBots: &[]SCTeamMember{}, } var scSettings *SCTeamSettings if settings.Open { settingsTemp, err := CreateTeamSettings(settings.Open, settings.JoinAs) if err != nil { return nil, err } scSettings = &settingsTemp } teamID := name.ToPrivateTeamID() err = makeSigAndPostRootTeam(ctx, g, me, members, nil, secretboxRecipients, name.String(), teamID, false, false, scSettings, *merkleRoot) return &teamID, err } func CreateSubteam(ctx context.Context, g *libkb.GlobalContext, subteamBasename string, parentName keybase1.TeamName, addSelfAs keybase1.TeamRole, ) (ret *keybase1.TeamID, err error) { defer g.CTrace(ctx, "CreateSubteam", &err)() mctx := libkb.NewMetaContext(ctx, g) subteamName, err := parentName.Append(subteamBasename) if err != nil { return nil, err } // Assume private public := false subteamID := NewSubteamID(public) err = ForceMerkleRootUpdateByTeamID(mctx, parentName.ToPrivateTeamID()) if err != nil { return nil, err } merkleRoot, err := g.MerkleClient.FetchRootFromServer(libkb.NewMetaContext(ctx, g), libkb.TeamMerkleFreshnessForAdmin) if err != nil { return nil, err } perUserKeyUpgradeSoft(ctx, mctx.G(), "create-subteam") me, err := loadMeForSignatures(ctx, mctx.G()) if err != nil { return nil, err } deviceSigningKey, err := mctx.G().ActiveDevice.SigningKey() if err != nil { return nil, err } parentTeam, err := GetForTeamManagementByStringName(ctx, g, parentName.String(), true) if err != nil { return nil, err } admin, err := parentTeam.getAdminPermission(ctx) if err != nil { return nil, err } if err := parentTeam.ForceMerkleRootUpdate(ctx); err != nil { return nil, err } // Subteam creation involves two links, one in the parent team's chain, and // one to start the new subteam chain. The start of the new subteam chain // (type "team.subteam_head") is very similar to the "team.root" sig that // starts a root team, and so making that link is very similar to what the // CreateTeamEngine does. newSubteamSig, ratchet, err := generateNewSubteamSigForParentChain(mctx, me, deviceSigningKey, parentTeam.chain(), subteamName, subteamID, admin) if err != nil { return nil, err } // subteam needs to be keyed for all admins above it allParentAdmins, err := parentTeam.AllAdmins(ctx) if err != nil { return nil, err } subteamHeadSig, secretboxes, err := generateHeadSigForSubteamChain(ctx, g, me, deviceSigningKey, parentTeam.chain(), subteamName, subteamID, admin, allParentAdmins, addSelfAs, *merkleRoot) if err != nil { return nil, err } err = precheckLinkToPost(ctx, g, *newSubteamSig, parentTeam.chain(), me.ToUserVersion()) if err != nil { return nil, fmt.Errorf("cannot post link (precheck new subteam): %v", err) } err = precheckLinkToPost(ctx, g, *subteamHeadSig, nil, me.ToUserVersion()) if err != nil { return nil, fmt.Errorf("cannot post link (precheck subteam head): %v", err) } payload := make(libkb.JSONPayload) payload["sigs"] = []any{newSubteamSig, subteamHeadSig} payload["per_team_key"] = secretboxes ratchet.AddToJSONPayload(payload) _, err = g.API.PostJSON(mctx, libkb.APIArg{ Endpoint: "sig/multi", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, }) if err != nil { return nil, err } return &subteamID, nil } func makeRootTeamSection(teamName string, teamID keybase1.TeamID, members SCTeamMembers, invites *SCTeamInvites, perTeamSigningKID keybase1.KID, perTeamEncryptionKID keybase1.KID, public bool, implicit bool, settings *SCTeamSettings, ) (SCTeamSection, error) { teamSection := SCTeamSection{ Name: (*SCTeamName)(&teamName), ID: (SCTeamID)(teamID), Public: public, Implicit: implicit, PerTeamKey: &SCPerTeamKey{ Generation: 1, SigKID: perTeamSigningKID, EncKID: perTeamEncryptionKID, }, Members: &members, Invites: invites, } teamSection.Settings = settings // At this point the team section has every field filled out except the // reverse sig. Now we'll wrap it into a full sig, marshal it to JSON, and // sign it, *twice*. The first time with the per-team signing key, to // produce the reverse sig, and the second time with the device signing // key, after the reverse sig has been written in. return teamSection, nil } func generateNewSubteamSigForParentChain(m libkb.MetaContext, me libkb.UserForSignatures, signingKey libkb.GenericKey, parentTeam *TeamSigChainState, subteamName keybase1.TeamName, subteamID keybase1.TeamID, admin *SCTeamAdmin) (item *libkb.SigMultiItem, r *hidden.Ratchet, err error) { newSubteamSigBody, ratchet, err := NewSubteamSig(m, me, signingKey, parentTeam, subteamName, subteamID, admin) if err != nil { return nil, nil, err } newSubteamSigJSON, err := newSubteamSigBody.Marshal() if err != nil { return nil, nil, err } prevLinkID, err := libkb.ImportLinkID(parentTeam.GetLatestLinkID()) if err != nil { return nil, nil, err } seqType := seqTypeForTeamPublicness(parentTeam.IsPublic()) v2Sig, _, _, err := libkb.MakeSigchainV2OuterSig( m, signingKey, libkb.LinkTypeNewSubteam, parentTeam.GetLatestSeqno()+1, newSubteamSigJSON, prevLinkID, libkb.SigHasRevokes(false), seqType, libkb.SigIgnoreIfUnsupported(false), nil, ) if err != nil { return nil, nil, err } item = &libkb.SigMultiItem{ Sig: v2Sig, SigningKID: signingKey.GetKID(), Type: string(libkb.LinkTypeNewSubteam), SeqType: seqType, SigInner: string(newSubteamSigJSON), TeamID: parentTeam.GetID(), Version: libkb.KeybaseSignatureV2, } return item, ratchet, nil } func generateHeadSigForSubteamChain(ctx context.Context, g *libkb.GlobalContext, me libkb.UserForSignatures, signingKey libkb.GenericKey, parentTeam *TeamSigChainState, subteamName keybase1.TeamName, subteamID keybase1.TeamID, admin *SCTeamAdmin, allParentAdmins []keybase1.UserVersion, addSelfAs keybase1.TeamRole, merkleRoot libkb.MerkleRoot, ) (item *libkb.SigMultiItem, boxes *PerTeamSharedSecretBoxes, err error) { deviceEncryptionKey, err := g.ActiveDevice.EncryptionKey() if err != nil { return } members := SCTeamMembers{ Owners: &[]SCTeamMember{}, Admins: &[]SCTeamMember{}, Writers: &[]SCTeamMember{}, Readers: &[]SCTeamMember{}, Bots: &[]SCTeamMember{}, RestrictedBots: &[]SCTeamMember{}, } memSet := newMemberSet() _, err = memSet.loadGroup(ctx, g, allParentAdmins, storeMemberKindRecipient, true /* force poll */) if err != nil { return nil, nil, err } if addSelfAs != keybase1.TeamRole_NONE { meUV := me.ToUserVersion() memList := []SCTeamMember{SCTeamMember(meUV)} switch addSelfAs { case keybase1.TeamRole_BOT: members.Bots = &memList case keybase1.TeamRole_READER: members.Readers = &memList case keybase1.TeamRole_WRITER: members.Writers = &memList case keybase1.TeamRole_ADMIN: members.Admins = &memList case keybase1.TeamRole_OWNER: return nil, nil, errors.New("Cannot add self as owner to a subteam") case keybase1.TeamRole_RESTRICTEDBOT: return nil, nil, errors.New("Cannot add self as restricted bot to a subteam") } _, err := memSet.addMember(ctx, g, meUV, storeMemberKindRecipient, false /* force poll */) if err != nil { return nil, nil, err } } // These boxes will get posted along with the sig below. m, err := NewTeamKeyManager(g, subteamID) if err != nil { return nil, nil, err } boxes, err = m.SharedSecretBoxes(libkb.NewMetaContext(ctx, g), deviceEncryptionKey, memSet.recipients) if err != nil { return nil, nil, err } perTeamSigningKey, err := m.SigningKey() if err != nil { return nil, nil, err } perTeamEncryptionKey, err := m.EncryptionKey() if err != nil { return nil, nil, err } // The "team" section of a subchain head link is similar to that of a // "team.root" link, with the addition of the "parent" subsection. teamSection, err := makeSubteamTeamSection(subteamName, subteamID, parentTeam, members, perTeamSigningKey.GetKID(), perTeamEncryptionKey.GetKID(), admin) if err != nil { return } err = addSummaryHash(&teamSection, boxes) if err != nil { return nil, nil, err } subteamHeadSigBodyBeforeReverse, err := SubteamHeadSig(g, me, signingKey, teamSection, merkleRoot) if err != nil { return } // Now generate the reverse sig and edit it into the JSON. Note that this // (sigchain-v1-style) reverse sig is made with the derived *per-team* // signing key. reverseSig, _, _, err := libkb.SignJSON(subteamHeadSigBodyBeforeReverse, perTeamSigningKey) if err != nil { return } // Update the team section to include the reverse sig, sign it again, and // make a sigchain-v2-style sig out of it. Doing it this way, instead of // generating it twice with different parameters, makes it less likely to // accidentally capture different global state (like ctime and merkle // seqno). subteamHeadSigBodyAfterReverse := subteamHeadSigBodyBeforeReverse err = subteamHeadSigBodyAfterReverse.SetValueAtPath("body.team.per_team_key.reverse_sig", jsonw.NewString(reverseSig)) if err != nil { return nil, nil, err } subteamHeadSigJSON, err := subteamHeadSigBodyAfterReverse.Marshal() if err != nil { return } seqType := seqTypeForTeamPublicness(parentTeam.IsPublic()) v2Sig, _, _, err := libkb.MakeSigchainV2OuterSig( libkb.NewMetaContext(ctx, g), signingKey, libkb.LinkTypeSubteamHead, 1, /* seqno */ subteamHeadSigJSON, nil, /* prevLinkID */ libkb.SigHasRevokes(false), seqType, libkb.SigIgnoreIfUnsupported(false), nil, ) if err != nil { return } item = &libkb.SigMultiItem{ Sig: v2Sig, SigningKID: signingKey.GetKID(), Type: string(libkb.LinkTypeSubteamHead), SeqType: seqType, SigInner: string(subteamHeadSigJSON), TeamID: subteamID, PublicKeys: &libkb.SigMultiItemPublicKeys{ Encryption: perTeamEncryptionKey.GetKID(), Signing: perTeamSigningKey.GetKID(), }, } return } func makeSubteamTeamSection(subteamName keybase1.TeamName, subteamID keybase1.TeamID, parentTeam *TeamSigChainState, members SCTeamMembers, perTeamSigningKID keybase1.KID, perTeamEncryptionKID keybase1.KID, admin *SCTeamAdmin, ) (SCTeamSection, error) { subteamName2 := subteamName.String() teamSection := SCTeamSection{ Name: (*SCTeamName)(&subteamName2), ID: (SCTeamID)(subteamID), Parent: &SCTeamParent{ ID: SCTeamID(parentTeam.GetID()), Seqno: parentTeam.GetLatestSeqno() + 1, // the seqno of the *new* parent link SeqType: seqTypeForTeamPublicness(parentTeam.IsPublic()), }, PerTeamKey: &SCPerTeamKey{ Generation: 1, SigKID: perTeamSigningKID, EncKID: perTeamEncryptionKID, }, Members: &members, Admin: admin, } // At this point the team section has every field filled out except the // reverse sig. Next we'll wrap it into a full sig body, marshal it to // JSON, and sign it, *twice*. The first time with the per-team signing // key, to produce the reverse sig, and the second time with the device // signing key, after the reverse sig has been written in. return teamSection, nil } // Get a per-user key. // Wait for attempt but only warn on error. func perUserKeyUpgradeSoft(ctx context.Context, g *libkb.GlobalContext, reason string) { m := libkb.NewMetaContext(ctx, g) arg := &engine.PerUserKeyUpgradeArgs{} eng := engine.NewPerUserKeyUpgrade(g, arg) err := engine.RunEngine2(m, eng) if err != nil { m.Debug("PerUserKeyUpgrade failed (%s): %v", reason, err) } }