github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/client/cmd_login.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  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"golang.org/x/net/context"
    14  
    15  	"github.com/keybase/cli"
    16  	"github.com/keybase/client/go/libcmdline"
    17  	"github.com/keybase/client/go/libkb"
    18  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    19  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    20  )
    21  
    22  func NewCmdLogin(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
    23  	flags := []cli.Flag{
    24  		cli.BoolFlag{
    25  			Name:  "s, switch",
    26  			Usage: "switch out the current user for another",
    27  		},
    28  		cli.StringFlag{
    29  			Name:  "paperkey",
    30  			Usage: "DANGEROUS: automatically provision using this paper key",
    31  		},
    32  		cli.StringFlag{
    33  			Name:  "devicename",
    34  			Usage: "Device name used in automated provisioning",
    35  		},
    36  	}
    37  	cmd := cli.Command{
    38  		Name:         "login",
    39  		ArgumentHelp: "[username]",
    40  		Usage:        "Establish a session with the keybase server",
    41  		Description: `"keybase login" allows you to authenticate your local service against
    42  the keybase server. By default this runs an interactive flow, but
    43  you can automate this if your service has never been logged into
    44  a particular account before and the account has a paper key - in order
    45  to do so, pass the username as an argument, your desired unique device
    46  name as the "-devicename" flag and pass the paper key as the standard
    47  input. Alternatively, these parameters can be passed as "KEYBASE_PAPERKEY"
    48  and "KEYBASE_DEVICENAME" environment variables.`,
    49  		Action: func(c *cli.Context) {
    50  			cl.ChooseCommand(NewCmdLoginRunner(g), "login", c)
    51  		},
    52  		Flags: flags,
    53  	}
    54  
    55  	// Note we'll only be able to set this via mode via Environment variable
    56  	// since it's too early to check command-line setting of it.
    57  	if g.Env.GetRunMode() == libkb.DevelRunMode {
    58  		cmd.Flags = append(cmd.Flags, cli.BoolFlag{
    59  			Name:  "emulate-gui",
    60  			Usage: "emulate GUI signing and fork GPG from the service",
    61  		})
    62  	}
    63  	return cmd
    64  }
    65  
    66  type CmdLogin struct {
    67  	libkb.Contextified
    68  	Username     string
    69  	doUserSwitch bool
    70  
    71  	PaperKey   string
    72  	DeviceName string
    73  
    74  	clientType keybase1.ClientType
    75  	cancel     func()
    76  	done       chan struct{}
    77  	SessionID  int
    78  }
    79  
    80  func NewCmdLoginRunner(g *libkb.GlobalContext) *CmdLogin {
    81  	return &CmdLogin{
    82  		Contextified: libkb.NewContextified(g),
    83  		clientType:   keybase1.ClientType_CLI,
    84  		done:         make(chan struct{}, 1),
    85  	}
    86  }
    87  
    88  func (c *CmdLogin) Run() error {
    89  	protocols := []rpc.Protocol{
    90  		NewProvisionUIProtocol(c.G(), libkb.KexRoleProvisionee),
    91  		NewLoginUIProtocol(c.G()),
    92  		NewSecretUIProtocol(c.G()),
    93  		NewGPGUIProtocol(c.G()),
    94  	}
    95  	if err := RegisterProtocolsWithContext(protocols, c.G()); err != nil {
    96  		return err
    97  	}
    98  	client, err := GetLoginClient(c.G())
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	// TODO: it would be nice to move this up a level and have keybase/main.go create
   104  	// a context and pass it to Command.Run(), then it can handle cancel itself
   105  	// instead of using Command.Cancel().
   106  	ctx, cancel := context.WithCancel(context.Background())
   107  	c.cancel = cancel
   108  	defer func() {
   109  		c.cancel()
   110  		c.cancel = nil
   111  	}()
   112  
   113  	var paperKey string
   114  	if c.DeviceName != "" {
   115  		paperKey, err = c.getPaperKey()
   116  		if err != nil {
   117  			return err
   118  		}
   119  	}
   120  
   121  	err = client.Login(ctx,
   122  		keybase1.LoginArg{
   123  			Username:     c.Username,
   124  			DeviceType:   keybase1.DeviceTypeV2_DESKTOP,
   125  			ClientType:   c.clientType,
   126  			SessionID:    c.SessionID,
   127  			DoUserSwitch: c.doUserSwitch,
   128  
   129  			PaperKey:   paperKey,
   130  			DeviceName: c.DeviceName,
   131  		})
   132  	c.done <- struct{}{}
   133  
   134  	// Provide explicit error messages for these cases.
   135  	switch x := err.(type) {
   136  	case libkb.NoSyncedPGPKeyError:
   137  		err = c.errNoSyncedKey()
   138  	case libkb.PassphraseError:
   139  		err = c.errPassphrase()
   140  	case libkb.NoMatchingGPGKeysError:
   141  		err = c.errNoMatchingGPGKeys(x.Fingerprints)
   142  	case libkb.DeviceAlreadyProvisionedError:
   143  		err = c.errDeviceAlreadyProvisioned()
   144  	case libkb.ProvisionUnavailableError:
   145  		err = c.errProvisionUnavailable()
   146  	case libkb.GPGUnavailableError:
   147  		err = c.errGPGUnavailable()
   148  	case libkb.NotFoundError:
   149  		err = c.errNotFound()
   150  	}
   151  
   152  	return err
   153  }
   154  
   155  func (c *CmdLogin) ParseArgv(ctx *cli.Context) error {
   156  	nargs := len(ctx.Args())
   157  	if nargs > 1 {
   158  		return errors.New("Invalid arguments.")
   159  	}
   160  
   161  	if nargs == 1 {
   162  		c.Username = ctx.Args()[0]
   163  		checker := libkb.CheckUsername
   164  		if !checker.F(c.Username) {
   165  			return fmt.Errorf("Invalid username. Valid usernames are: %s", checker.Hint)
   166  		}
   167  	}
   168  	c.doUserSwitch = ctx.Bool("switch")
   169  
   170  	c.PaperKey = c.getOption(ctx, "paperkey")
   171  	c.DeviceName = c.getOption(ctx, "devicename")
   172  
   173  	return nil
   174  }
   175  
   176  func (c *CmdLogin) getOption(ctx *cli.Context, s string) string {
   177  	v := ctx.String(s)
   178  	if len(v) > 0 {
   179  		return v
   180  	}
   181  	envVarName := fmt.Sprintf("KEYBASE_%s", strings.ToUpper(strings.ReplaceAll(s, "-", "_")))
   182  	v = os.Getenv(envVarName)
   183  	if len(v) > 0 {
   184  		return v
   185  	}
   186  	return ""
   187  }
   188  
   189  func (c *CmdLogin) GetUsage() libkb.Usage {
   190  	return libkb.Usage{
   191  		Config:    true,
   192  		KbKeyring: true,
   193  		API:       true,
   194  	}
   195  }
   196  
   197  func (c *CmdLogin) Cancel() error {
   198  	c.G().Log.Debug("received request to cancel running login command")
   199  	if c.cancel != nil {
   200  		c.G().Log.Debug("command cancel function exists, calling it")
   201  		c.cancel()
   202  
   203  		// In go-framed-msgpack-rpc, dispatch.handleCall() starts a goroutine to check the context being
   204  		// canceled.
   205  		// So, need to wait here for call to actually finish in order for the cancel message to make it
   206  		// to the daemon.
   207  		select {
   208  		case <-c.done:
   209  			c.G().Log.Debug("command finished, cancel complete")
   210  		case <-time.After(5 * time.Second):
   211  			c.G().Log.Debug("timed out waiting for command to finish")
   212  		}
   213  	}
   214  	return nil
   215  }
   216  
   217  func (c *CmdLogin) getPaperKey() (ret string, err error) {
   218  	if len(c.PaperKey) > 0 {
   219  		return c.PaperKey, nil
   220  	}
   221  	ret, err = c.G().UI.GetTerminalUI().PromptPasswordMaybeScripted(PromptDescriptorPaperKey, "paper key: ")
   222  	return ret, err
   223  }
   224  
   225  func (c *CmdLogin) errNoSyncedKey() error {
   226  	return errors.New(`in Login
   227  
   228  Sorry, your account is already established with a PGP public key, but this
   229  utility cannot access the corresponding private key. You need to prove
   230  you're you. We suggest one of the following:
   231  
   232     - install GPG and put your PGP private key on this machine and try again
   233     - reset your account and start fresh: https://keybase.io/#account-reset
   234     - go back and provision with another device or paper key
   235  `)
   236  }
   237  
   238  func (c *CmdLogin) errPassphrase() error {
   239  	return errors.New(`in Login
   240  
   241  The server rejected your login attempt.
   242  
   243  If you'd like to reset your passphrase, go to https://keybase.io/#password-reset
   244  `)
   245  }
   246  
   247  func (c *CmdLogin) errNoMatchingGPGKeys(fingerprints []string) error {
   248  	plural := len(fingerprints) > 1
   249  
   250  	first := "Sorry, your account is already established with a PGP public key, but this\nutility cannot find the corresponding private key on this machine."
   251  	pre := "This is the fingerprint of the PGP key in your account:"
   252  	if plural {
   253  		first = "Sorry, your account is already established with PGP public keys, but this\nutility cannot find a corresponding private key on this machine."
   254  		pre = "These are the fingerprints of the PGP keys in your account:"
   255  	}
   256  
   257  	fpsIndent := make([]string, len(fingerprints))
   258  	for i, fp := range fingerprints {
   259  		fpsIndent[i] = "   " + fp
   260  	}
   261  
   262  	after := `You need to prove you're you. We suggest one of the following:
   263  
   264     - put one of the PGP private keys listed above on this machine and try again
   265     - reset your account and start fresh: https://keybase.io/#account-reset
   266  `
   267  
   268  	out := first + "\n" + pre + "\n\n" + strings.Join(fpsIndent, "\n") + "\n\n" + after
   269  	return errors.New(out)
   270  }
   271  
   272  func (c *CmdLogin) errDeviceAlreadyProvisioned() error {
   273  	return errors.New(`in Login
   274  
   275  You have already provisioned this device. Please use 'keybase login [username]'
   276  to log in.
   277  `)
   278  }
   279  
   280  func (c *CmdLogin) errProvisionUnavailable() error {
   281  	return errors.New(`in Login
   282  
   283  The only way to provision this device is with access to one of your existing
   284  devices. You can try again later, or if you have lost access to all your
   285  existing devices you can reset your account and start fresh.
   286  
   287  If you'd like to reset your account:  https://keybase.io/#account-reset
   288  `)
   289  }
   290  
   291  func (c *CmdLogin) errGPGUnavailable() error {
   292  	return errors.New(`in Login
   293  
   294  Sorry, your account is already established with a PGP public key, but this
   295  utility cannot access the corresponding private key. You need to prove
   296  you're you. We suggest one of the following:
   297  
   298     - install GPG and put your PGP private key on this machine and try again
   299     - reset your account and start fresh: https://keybase.io/#account-reset
   300  `)
   301  }
   302  
   303  func (c *CmdLogin) errNotFound() error {
   304  	return errors.New(`in Login
   305  
   306  This username doesn't exist.`)
   307  }