github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/gpg_import_key.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 engine 5 6 // 7 // engine.GPGImportKeyEngine is a class that selects key from the GPG keyring via 8 // shell-out to the gpg command line client. It's useful in `client mykey select` 9 // and other places in which the user picks existing PGP keys on the existing 10 // system for use in Keybase tasks. 11 // 12 13 import ( 14 "fmt" 15 16 "github.com/keybase/client/go/libkb" 17 keybase1 "github.com/keybase/client/go/protocol/keybase1" 18 ) 19 20 type GPGImportKeyArg struct { 21 Query string 22 Signer libkb.GenericKey 23 AllowMulti bool 24 SkipImport bool 25 OnlyImport bool 26 HasProvisionedDevice bool 27 Me *libkb.User 28 Lks *libkb.LKSec 29 } 30 31 type GPGImportKeyEngine struct { 32 last *libkb.PGPKeyBundle 33 arg *GPGImportKeyArg 34 duplicatedFingerprints []libkb.PGPFingerprint 35 libkb.Contextified 36 } 37 38 func NewGPGImportKeyEngine(g *libkb.GlobalContext, arg *GPGImportKeyArg) *GPGImportKeyEngine { 39 return &GPGImportKeyEngine{ 40 arg: arg, 41 Contextified: libkb.NewContextified(g), 42 } 43 } 44 45 func (e *GPGImportKeyEngine) Prereqs() Prereqs { 46 if !e.arg.HasProvisionedDevice { 47 return Prereqs{TemporarySession: true} 48 } 49 return Prereqs{Device: true} 50 } 51 52 func (e *GPGImportKeyEngine) Name() string { 53 return "GPGImportKeyEngine" 54 } 55 56 func (e *GPGImportKeyEngine) RequiredUIs() []libkb.UIKind { 57 return []libkb.UIKind{ 58 libkb.GPGUIKind, 59 libkb.SecretUIKind, 60 } 61 } 62 63 func (e *GPGImportKeyEngine) SubConsumers() []libkb.UIConsumer { 64 return []libkb.UIConsumer{ 65 &PGPKeyImportEngine{}, 66 &PGPUpdateEngine{}, 67 } 68 } 69 70 func (e *GPGImportKeyEngine) WantsGPG(mctx libkb.MetaContext) (bool, error) { 71 gpg := e.G().GetGpgClient() 72 canExec, err := gpg.CanExec(mctx) 73 if err != nil { 74 return false, err 75 } 76 if !canExec { 77 return false, nil 78 } 79 80 // they have gpg 81 82 // get an index of all the secret keys 83 index, _, err := gpg.Index(mctx, true, "") 84 if err != nil { 85 return false, err 86 } 87 if index.Len() == 0 { 88 // no private keys available, so don't offer 89 return false, nil 90 } 91 92 res, err := mctx.UIs().GPGUI.WantToAddGPGKey(mctx.Ctx(), 0) 93 if err != nil { 94 return false, err 95 } 96 return res, nil 97 } 98 99 func (e *GPGImportKeyEngine) Run(mctx libkb.MetaContext) (err error) { 100 gpg := e.G().GetGpgClient() 101 102 me := e.arg.Me 103 if me == nil { 104 if me, err = libkb.LoadMe(libkb.NewLoadUserPubOptionalArg(e.G())); err != nil { 105 return err 106 } 107 } 108 109 if !e.arg.OnlyImport { 110 if err = PGPCheckMulti(me, e.arg.AllowMulti); err != nil { 111 return err 112 } 113 } 114 115 if err = gpg.Configure(mctx); err != nil { 116 return err 117 } 118 index, warns, err := gpg.Index(mctx, true, e.arg.Query) 119 if err != nil { 120 return err 121 } 122 warns.Warn(e.G()) 123 124 var gks []keybase1.GPGKey 125 for _, key := range index.Keys { 126 gk := keybase1.GPGKey{ 127 Algorithm: fmt.Sprintf("%d%s", key.Bits, key.AlgoString()), 128 KeyID: key.GetFingerprint().ToKeyID(), 129 Expiration: key.ExpirationString(), 130 Identities: key.GetPGPIdentities(), 131 } 132 gks = append(gks, gk) 133 } 134 135 if len(gks) == 0 { 136 return fmt.Errorf("No PGP keys available to choose from.") 137 } 138 139 res, err := mctx.UIs().GPGUI.SelectKeyAndPushOption(mctx.Ctx(), keybase1.SelectKeyAndPushOptionArg{Keys: gks}) 140 if err != nil { 141 return err 142 } 143 mctx.Debug("SelectKey result: %+v", res) 144 145 var selected *libkb.GpgPrimaryKey 146 for _, key := range index.Keys { 147 if key.GetFingerprint().ToKeyID() == res.KeyID { 148 selected = key 149 break 150 } 151 } 152 153 if selected == nil { 154 return nil 155 } 156 157 publicKeys := me.GetActivePGPKeys(false) 158 duplicate := false 159 for _, key := range publicKeys { 160 if key.GetFingerprint().Eq(*(selected.GetFingerprint())) { 161 duplicate = true 162 break 163 } 164 } 165 if duplicate && !e.arg.OnlyImport { 166 // This key's already been posted to the server. 167 res, err := mctx.UIs().GPGUI.ConfirmDuplicateKeyChosen(mctx.Ctx(), 0) 168 if err != nil { 169 return err 170 } 171 if !res { 172 return libkb.SibkeyAlreadyExistsError{} 173 } 174 // We're sending a key update, then. 175 fp := selected.GetFingerprint().String() 176 eng := NewPGPUpdateEngine(e.G(), []string{fp}, false) 177 err = RunEngine2(mctx, eng) 178 e.duplicatedFingerprints = eng.duplicatedFingerprints 179 180 if err != nil { 181 return err 182 } 183 184 if !e.arg.SkipImport { 185 // Key is duplicate, but caller wants to import secret 186 // half. 187 res, err := mctx.UIs().GPGUI.ConfirmImportSecretToExistingKey(mctx.Ctx(), 0) 188 if err != nil { 189 return err 190 } 191 if !res { 192 // But update itself has finished, so this 193 // cancellation is not an error. 194 mctx.Info("User cancelled secret key import.") 195 return nil 196 } 197 // Fall through with OnlyImport=true so it skips sig posting 198 // (which would be rejected because of duplicate kid). 199 e.arg.OnlyImport = true 200 } else { 201 // Nothing to more do. 202 return nil 203 } 204 } 205 206 tty, err := mctx.UIs().GPGUI.GetTTY(mctx.Ctx()) 207 if err != nil { 208 mctx.Warning("error getting TTY for GPG: %s", err) 209 err = nil 210 } 211 212 var bundle *libkb.PGPKeyBundle 213 214 if e.arg.SkipImport { 215 // If we don't need secret key to save in Keybase keyring, 216 // just import public key and rely on GPG fallback for reverse 217 // signature. 218 bundle, err = gpg.ImportKey(mctx, false, *(selected.GetFingerprint()), tty) 219 if err != nil { 220 return fmt.Errorf("ImportKey (secret: false) error: %s", err) 221 } 222 } else { 223 bundle, err = gpg.ImportKey(mctx, true, *(selected.GetFingerprint()), tty) 224 if err != nil { 225 return fmt.Errorf("ImportKey (secret: true) error: %s", err) 226 } 227 228 if err := bundle.Unlock(mctx, "Import of key into Keybase keyring", mctx.UIs().SecretUI); err != nil { 229 return err 230 } 231 232 if !libkb.FindPGPPrivateKey(bundle) { 233 return PGPImportStubbedError{KeyIDString: selected.GetFingerprint().ToKeyID()} 234 } 235 } 236 237 if e.arg.OnlyImport { 238 if err := e.ensurePublicPartIsPublished(me, bundle.GetKID()); err != nil { 239 // Make sure key is active in user's sigchain. Otherwise, 240 // after importing to local keychain, Keybase will refuse 241 // to use it for any operation anyway. 242 return err 243 } 244 } 245 246 mctx.Debug("Bundle unlocked: %s", selected.GetFingerprint().ToKeyID()) 247 248 eng := NewPGPKeyImportEngine(mctx.G(), PGPKeyImportEngineArg{ 249 Pregen: bundle, 250 SigningKey: e.arg.Signer, 251 Me: me, 252 AllowMulti: e.arg.AllowMulti, 253 NoSave: e.arg.SkipImport, 254 OnlySave: e.arg.OnlyImport, 255 Lks: e.arg.Lks, 256 GPGFallback: true, 257 }) 258 259 if err = RunEngine2(mctx, eng); err != nil { 260 261 // It's important to propagate a CanceledError unmolested, 262 // since the UI needs to know that. See: 263 // https://github.com/keybase/client/issues/226 264 if _, ok := err.(libkb.CanceledError); !ok { 265 err = libkb.KeyGenError{Msg: err.Error()} 266 } 267 return 268 } 269 270 mctx.Debug("Key %s imported", selected.GetFingerprint().ToKeyID()) 271 272 e.last = bundle 273 274 return nil 275 } 276 277 func (e *GPGImportKeyEngine) LastKey() *libkb.PGPKeyBundle { 278 return e.last 279 } 280 281 func (e *GPGImportKeyEngine) ensurePublicPartIsPublished(me *libkb.User, kid keybase1.KID) error { 282 ckf := me.GetComputedKeyFamily() 283 if ckf == nil { 284 return fmt.Errorf("cannot get ComputedKeyFamily") 285 } 286 active := ckf.GetKeyRole(kid) 287 if active != libkb.DLGSibkey { 288 return PGPNotActiveForLocalImport{kid} 289 } 290 return nil 291 }