github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/client/cmd_dump_keyfamily.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package client 5 6 import ( 7 "fmt" 8 "strings" 9 10 "golang.org/x/net/context" 11 12 "github.com/keybase/cli" 13 "github.com/keybase/client/go/libcmdline" 14 "github.com/keybase/client/go/libkb" 15 keybase1 "github.com/keybase/client/go/protocol/keybase1" 16 ) 17 18 const spacesPerIndent = 4 19 20 func indentSpace(level int) string { 21 return strings.Repeat(" ", level*spacesPerIndent) 22 } 23 24 type CmdDumpKeyfamily struct { 25 libkb.Contextified 26 user string 27 } 28 29 func (v *CmdDumpKeyfamily) ParseArgv(ctx *cli.Context) error { 30 nargs := len(ctx.Args()) 31 if nargs > 1 { 32 return fmt.Errorf("dump-keyfamily only takes one argument, the user to lookup") 33 } 34 if nargs == 1 { 35 v.user = ctx.Args()[0] 36 } 37 return nil 38 } 39 40 func NewCmdDumpKeyfamily(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command { 41 return cli.Command{ 42 Name: "dump-keyfamily", 43 Unlisted: true, 44 ArgumentHelp: "[username]", 45 Usage: "Print out a user's current key family", 46 Description: "Print out a user's current key family. Don't specify a username to dump out your own keys.", 47 Flags: []cli.Flag{}, 48 Action: func(c *cli.Context) { 49 cl.ChooseCommand(&CmdDumpKeyfamily{Contextified: libkb.NewContextified(g)}, "dump-keyfamily", c) 50 }, 51 } 52 } 53 54 func (v *CmdDumpKeyfamily) Run() (err error) { 55 configCli, err := GetConfigClient(v.G()) 56 if err != nil { 57 return err 58 } 59 60 currentStatus, err := configCli.GetCurrentStatus(context.TODO(), 0) 61 if err != nil { 62 return err 63 } 64 if !currentStatus.LoggedIn { 65 return fmt.Errorf("Not logged in.") 66 } 67 68 var username string 69 if v.user != "" { 70 username = v.user 71 } else { 72 username = currentStatus.User.Username 73 } 74 75 userCli, err := GetUserClient(v.G()) 76 if err != nil { 77 return err 78 } 79 80 user, err := userCli.LoadUserByName(context.TODO(), keybase1.LoadUserByNameArg{Username: username}) 81 if err != nil { 82 return fmt.Errorf("error loading user: %s", err) 83 } 84 85 publicKeys, err := userCli.LoadPublicKeys(context.TODO(), keybase1.LoadPublicKeysArg{Uid: user.Uid}) 86 if err != nil { 87 return fmt.Errorf("error loading keys: %s", err) 88 } 89 90 devCli, err := GetDeviceClient(v.G()) 91 if err != nil { 92 return err 93 } 94 devs, err := devCli.DeviceList(context.TODO(), 0) 95 if err != nil { 96 return fmt.Errorf("error loading device list: %s", err) 97 } 98 99 return v.printExportedUser(user, publicKeys, devs) 100 } 101 102 func findSubkeys(parentID keybase1.KID, allKeys []keybase1.PublicKey) []keybase1.PublicKey { 103 ret := []keybase1.PublicKey{} 104 for _, key := range allKeys { 105 if keybase1.KIDFromString(key.ParentID).Equal(parentID) { 106 ret = append(ret, key) 107 } 108 } 109 return ret 110 } 111 112 func (v *CmdDumpKeyfamily) printExportedUser(user keybase1.User, publicKeys []keybase1.PublicKey, 113 devices []keybase1.Device) error { 114 115 dui := v.G().UI.GetDumbOutputUI() 116 if len(publicKeys) == 0 { 117 dui.Printf("No public keys.\n") 118 return nil 119 } 120 dui.Printf("Public keys:\n") 121 // Keep track of subkeys we print, so that if e.g. a subkey's parent is 122 // nonexistent, we can notice that we skipped it. 123 subkeysShown := make(map[keybase1.KID]bool) 124 for _, key := range publicKeys { 125 if !key.IsSibkey { 126 // Subkeys will be printed under their respective sibkeys. 127 continue 128 } 129 subkeys := findSubkeys(key.KID, publicKeys) 130 err := v.printKey(key, subkeys, 1) 131 if err != nil { 132 return err 133 } 134 for _, subkey := range subkeys { 135 subkeysShown[subkey.KID] = true 136 } 137 } 138 // Print errors for any subkeys we failed to show. 139 for _, key := range publicKeys { 140 if !key.IsSibkey && !subkeysShown[key.KID] { 141 v.G().Log.Errorf("Dangling subkey: %s", key.KID) 142 } 143 } 144 return nil 145 } 146 147 func (v *CmdDumpKeyfamily) printKey(key keybase1.PublicKey, subkeys []keybase1.PublicKey, indent int) error { 148 if key.KID == "" { 149 return fmt.Errorf("Found a key with an empty KID.") 150 } 151 eldestStr := "" 152 if key.IsEldest { 153 eldestStr = " (eldest)" 154 } 155 dui := v.G().UI.GetDumbOutputUI() 156 dui.Printf("%s%s%s\n", indentSpace(indent), key.KID, eldestStr) 157 if key.PGPFingerprint != "" { 158 dui.Printf("%sPGP Fingerprint: %s\n", indentSpace(indent+1), libkb.PGPFingerprintFromHexNoError(key.PGPFingerprint).ToQuads()) 159 dui.Printf("%sPGP Identities:\n", indentSpace(indent+1)) 160 for _, identity := range key.PGPIdentities { 161 commentStr := "" 162 if identity.Comment != "" { 163 commentStr = fmt.Sprintf(" (%s)", identity.Comment) 164 } 165 emailStr := "" 166 if identity.Email != "" { 167 emailStr = fmt.Sprintf(" <%s>", identity.Email) 168 } 169 dui.Printf("%s%s%s%s\n", indentSpace(indent+2), identity.Username, commentStr, emailStr) 170 } 171 } 172 if key.DeviceID != "" || key.DeviceType != keybase1.DeviceTypeV2_NONE || key.DeviceDescription != "" { 173 dui.Printf("%sDevice:\n", indentSpace(indent+1)) 174 if key.DeviceID != "" { 175 dui.Printf("%sID: %s\n", indentSpace(indent+2), key.DeviceID) 176 } 177 if key.DeviceType != keybase1.DeviceTypeV2_NONE { 178 dui.Printf("%sType: %s\n", indentSpace(indent+2), key.DeviceType) 179 } 180 if key.DeviceDescription != "" { 181 dui.Printf("%sDescription: %s\n", indentSpace(indent+2), key.DeviceDescription) 182 } 183 } 184 dui.Printf("%sCreated: %s\n", indentSpace(indent+1), keybase1.FromTime(key.CTime)) 185 dui.Printf("%sExpires: %s\n", indentSpace(indent+1), keybase1.FromTime(key.ETime)) 186 187 if len(subkeys) > 0 { 188 dui.Printf("%sSubkeys:\n", indentSpace(indent+1)) 189 for _, subkey := range subkeys { 190 err := v.printKey(subkey, nil, indent+2) 191 if err != nil { 192 return err 193 } 194 } 195 } 196 return nil 197 } 198 199 func (v *CmdDumpKeyfamily) GetUsage() libkb.Usage { 200 return libkb.Usage{ 201 Config: true, 202 API: true, 203 } 204 }