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  }