github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/client/cmd_pgp_gen.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  
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/keybase/cli"
    12  	"github.com/keybase/client/go/engine"
    13  	"github.com/keybase/client/go/libcmdline"
    14  	"github.com/keybase/client/go/libkb"
    15  	"github.com/keybase/client/go/protocol/keybase1"
    16  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    17  )
    18  
    19  type CmdPGPGen struct {
    20  	libkb.Contextified
    21  	arg engine.PGPKeyImportEngineArg
    22  }
    23  
    24  var SmallKey = 1024
    25  
    26  func (v *CmdPGPGen) ParseArgv(ctx *cli.Context) (err error) {
    27  	nargs := len(ctx.Args())
    28  	if nargs != 0 {
    29  		err = fmt.Errorf("pgp gen takes 0 args")
    30  	} else {
    31  		g := libkb.PGPGenArg{}
    32  		g.PGPUids = ctx.StringSlice("pgp-uid")
    33  		v.arg.DoExport = !ctx.Bool("no-export")
    34  		v.arg.AllowMulti = ctx.Bool("multi")
    35  		if ctx.Bool("debug") {
    36  			g.PrimaryBits = SmallKey
    37  			g.SubkeyBits = SmallKey
    38  		}
    39  		v.arg.Gen = &g
    40  	}
    41  	return err
    42  }
    43  
    44  // Why use CreatePGPIDs rather than MakeAllIds?
    45  func (v *CmdPGPGen) Run() (err error) {
    46  	protocols := []rpc.Protocol{
    47  		NewSecretUIProtocol(v.G()),
    48  	}
    49  	cli, err := GetPGPClient(v.G())
    50  	if err != nil {
    51  		return err
    52  	}
    53  	user, err := GetUserClient(v.G())
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	if err = RegisterProtocolsWithContext(protocols, v.G()); err != nil {
    59  		return err
    60  	}
    61  
    62  	// Prompt for user IDs if none given on command line
    63  	if len(v.arg.Gen.PGPUids) == 0 {
    64  		if err = v.propmptPGPIDs(); err != nil {
    65  			return err
    66  		}
    67  	} else if err = v.arg.Gen.CreatePGPIDs(); err != nil {
    68  		return err
    69  	}
    70  
    71  	passphraseState, err := user.LoadPassphraseState(context.TODO(), 0)
    72  	if err != nil {
    73  		return err
    74  	}
    75  	if passphraseState == keybase1.PassphraseState_KNOWN {
    76  		v.arg.PushSecret, err = v.G().UI.GetTerminalUI().PromptYesNo(PromptDescriptorPGPGenPushSecret, "Push an encrypted copy of your new secret key to the Keybase.io server?", libkb.PromptDefaultYes)
    77  		if err != nil {
    78  			return err
    79  		}
    80  	}
    81  	if v.arg.DoExport {
    82  		v.arg.ExportEncrypted, err = v.G().UI.GetTerminalUI().PromptYesNo(PromptDescriptorPGPGenEncryptSecret, "When exporting to the GnuPG keychain, encrypt private keys with a passphrase?", libkb.PromptDefaultYes)
    83  		if err != nil {
    84  			return err
    85  		}
    86  	}
    87  
    88  	err = cli.PGPKeyGen(context.TODO(), v.arg.Export())
    89  	err = AddPGPMultiInstructions(err)
    90  	return err
    91  }
    92  
    93  var CheckRealName = libkb.Checker{
    94  	F: func(s string) bool {
    95  		nameID, err := libkb.ParseIdentity(s)
    96  		if err != nil {
    97  			return false
    98  		}
    99  		return len(nameID.Username) > 0 && len(nameID.Comment) == 0 && len(nameID.Email) == 0
   100  	},
   101  	Hint: "for example: \"Ned Snowben\"",
   102  }
   103  
   104  var CheckOptionalEmail = libkb.Checker{
   105  	F: func(s string) bool {
   106  		if len(s) == 0 {
   107  			return true
   108  		}
   109  		return libkb.CheckEmail.F(s)
   110  	},
   111  	Hint: libkb.CheckEmail.Hint,
   112  }
   113  
   114  func (v *CmdPGPGen) propmptPGPIDs() (err error) {
   115  	id := libkb.Identity{}
   116  	prompt := "Enter your real name, which will be publicly visible in your new key"
   117  	id.Username, err = PromptWithChecker(PromptDescriptorPGPGenEnterID, v.G().UI.GetTerminalUI(), prompt, false, CheckRealName)
   118  	if err != nil {
   119  		return
   120  	}
   121  	// Email required for primary ID
   122  	prompt = "Enter a public email address for your key"
   123  	id.Email, err = PromptWithChecker(PromptDescriptorPGPGenEnterID, v.G().UI.GetTerminalUI(), prompt, false, libkb.CheckEmail)
   124  	if err != nil {
   125  		return
   126  	}
   127  	v.arg.Gen.Ids = append(v.arg.Gen.Ids, id)
   128  
   129  	emailsSeen := make(map[string]struct{})
   130  
   131  	emailsSeen[id.Email] = struct{}{}
   132  
   133  	idAdditional := libkb.Identity{
   134  		Username: id.Username,
   135  	}
   136  	prompt = "Enter another email address (or <enter> when done)"
   137  	for {
   138  		idAdditional.Email, err = PromptWithChecker(PromptDescriptorPGPGenEnterID, v.G().UI.GetTerminalUI(), prompt, false, CheckOptionalEmail)
   139  		if err != nil || len(idAdditional.Email) == 0 {
   140  			break
   141  		}
   142  
   143  		// Make sure it hasn't been added already
   144  		if _, ok := emailsSeen[idAdditional.Email]; ok {
   145  			v.G().Log.Warning("Email already applied to this key")
   146  			continue
   147  		}
   148  
   149  		emailsSeen[idAdditional.Email] = struct{}{}
   150  		v.arg.Gen.Ids = append(v.arg.Gen.Ids, idAdditional)
   151  	}
   152  
   153  	return
   154  }
   155  
   156  func AddPGPMultiInstructions(err error) error {
   157  	if err == nil {
   158  		return nil
   159  	}
   160  	if kee, ok := err.(libkb.KeyExistsError); ok {
   161  		return fmt.Errorf("You already have a PGP key registered (%s)\n"+
   162  			"Specify the `--multi` flag to override this check",
   163  			kee.Key.ToQuads())
   164  	}
   165  	// Not the right type. Return it as is.
   166  	return err
   167  }
   168  
   169  func NewCmdPGPGen(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
   170  	return cli.Command{
   171  		Name:  "gen",
   172  		Usage: "Generate a new PGP key and write to local secret keychain",
   173  		Flags: []cli.Flag{
   174  			cli.BoolFlag{
   175  				Name:  "d, debug",
   176  				Usage: "Generate small keys for debugging.",
   177  			},
   178  			cli.StringSliceFlag{
   179  				Name:  "pgp-uid",
   180  				Usage: "Specify custom PGP uid(s).",
   181  				Value: &cli.StringSlice{},
   182  			},
   183  			cli.BoolFlag{
   184  				Name:  "multi",
   185  				Usage: "Allow multiple PGP keys.",
   186  			},
   187  			cli.BoolFlag{
   188  				Name:  "no-export",
   189  				Usage: "Disable exporting of new keys to GPG keychain.",
   190  			},
   191  		},
   192  		Description: `"keybase pgp gen" generates a new PGP key for this account.
   193     In all cases, it signs the public key with an existing device key,
   194     and pushes the signature to the server. Thus, the user will have a
   195     publicly-visible "PGP device" after running this operation.
   196  
   197     The secret half of the PGP key is written by default to the user's
   198     local Keybase keychain and encrypted with the "local key security"
   199     (LKS) protocol. (For more information, try 'keybase help keyring').
   200  
   201     Also, by default, the public **and secret** halves of the new PGP key
   202     are exported to the local GnuPG keyring, if one is found. You can
   203     specify "--no-export" to stop the export of the newly generated key
   204     to the GnuPG keyring.
   205  
   206     On subsequent secret key accesses --- say for PGP decryption or
   207     for signing --- access to the local GnuPG keyring is not required.
   208     Rather, keybase will access the secret PGP key in its own local keychain.
   209  
   210     By default, the secret half of the PGP key is never exported off
   211     of the local system, but users have a choice via terminal prompt
   212     to select storage of their encrypted secret PGP key on the Keybase
   213     servers.`,
   214  		Action: func(c *cli.Context) {
   215  			cl.ChooseCommand(&CmdPGPGen{Contextified: libkb.NewContextified(g)}, "gen", c)
   216  		},
   217  	}
   218  }
   219  
   220  func (v *CmdPGPGen) GetUsage() libkb.Usage {
   221  	return libkb.Usage{
   222  		Config:    true,
   223  		KbKeyring: true,
   224  		API:       true,
   225  	}
   226  }