github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/signup.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  import (
     7  	"encoding/base64"
     8  	"fmt"
     9  
    10  	"github.com/keybase/client/go/libkb"
    11  	"github.com/keybase/client/go/protocol/keybase1"
    12  	triplesec "github.com/keybase/go-triplesec"
    13  )
    14  
    15  // For password-less signups, number of bytes that are randomly generated
    16  // and then encoded with base64 to be used as user's passphrase.
    17  const randomPassphraseLen = 16
    18  
    19  type SignupEngine struct {
    20  	pwsalt         []byte
    21  	ppStream       *libkb.PassphraseStream
    22  	tsec           libkb.Triplesec
    23  	uid            keybase1.UID
    24  	me             *libkb.User
    25  	signingKey     libkb.GenericKey
    26  	encryptionKey  libkb.NaclDHKeyPair
    27  	arg            *SignupEngineRunArg
    28  	lks            *libkb.LKSec
    29  	perUserKeyring *libkb.PerUserKeyring // Created after provisioning. Sent to paperkey gen.
    30  	paperKey       *libkb.PaperKeyPhrase
    31  }
    32  
    33  var _ Engine2 = (*SignupEngine)(nil)
    34  
    35  type SignupEngineRunArg struct {
    36  	Username                 string
    37  	Email                    string
    38  	InviteCode               string
    39  	Passphrase               string
    40  	GenerateRandomPassphrase bool
    41  	StoreSecret              bool
    42  	DeviceName               string
    43  	DeviceType               keybase1.DeviceType
    44  	SkipGPG                  bool
    45  	SkipMail                 bool
    46  	SkipPaper                bool
    47  	GenPGPBatch              bool // if true, generate and push a pgp key to the server (no interaction)
    48  	VerifyEmail              bool
    49  
    50  	// Bot signups have random PWs, no device keys, an eldest paper key, and return a paper key via
    51  	// the main flow; you need to supply a bot token to signup with them.
    52  	BotToken keybase1.BotToken
    53  
    54  	// Used in tests for reproducible key generation
    55  	naclSigningKeyPair    libkb.NaclKeyPair
    56  	naclEncryptionKeyPair libkb.NaclKeyPair
    57  }
    58  
    59  func NewSignupEngine(g *libkb.GlobalContext, arg *SignupEngineRunArg) *SignupEngine {
    60  	return &SignupEngine{
    61  		arg: arg,
    62  	}
    63  }
    64  
    65  func (s *SignupEngine) Name() string {
    66  	return "Signup"
    67  }
    68  
    69  func (s *SignupEngine) RequiredUIs() []libkb.UIKind {
    70  	return nil
    71  }
    72  
    73  func (s *SignupEngine) Prereqs() Prereqs { return Prereqs{} }
    74  
    75  func (s *SignupEngine) SubConsumers() []libkb.UIConsumer {
    76  	if s.arg.BotToken.Exists() {
    77  		return nil
    78  	}
    79  	return []libkb.UIConsumer{
    80  		&GPGImportKeyEngine{},
    81  		&DeviceWrap{},
    82  		&PaperKeyPrimary{},
    83  	}
    84  }
    85  
    86  func (s *SignupEngine) GetMe() *libkb.User {
    87  	return s.me
    88  }
    89  
    90  func (s *SignupEngine) PaperKey() *libkb.PaperKeyPhrase {
    91  	return s.paperKey
    92  }
    93  
    94  func (s *SignupEngine) Run(m libkb.MetaContext) (err error) {
    95  	defer m.Trace("SignupEngine#Run", &err)()
    96  
    97  	if err = m.LogoutKeepSecrets(); err != nil {
    98  		return err
    99  	}
   100  
   101  	// StoreSecret is required if we are doing NOPW
   102  	if !s.arg.StoreSecret && s.arg.GenerateRandomPassphrase && s.arg.BotToken.IsNil() {
   103  		return fmt.Errorf("cannot SignUp with StoreSecret=false and GenerateRandomPassphrase=true")
   104  	}
   105  
   106  	// check if secret store works
   107  	if s.arg.StoreSecret {
   108  		if ss := m.G().SecretStore(); ss != nil {
   109  			if s.arg.GenerateRandomPassphrase && !ss.IsPersistent() {
   110  				// IsPersistent returns true if SecretStoreLocked is
   111  				// disk-backed, and false if it's only memory backed.
   112  				return SecretStoreNotFunctionalError{err: fmt.Errorf("persistent secret store is required for no-passphrase signup")}
   113  			}
   114  
   115  			err = ss.PrimeSecretStores(m)
   116  			if err != nil {
   117  				return SecretStoreNotFunctionalError{err}
   118  			}
   119  		} else if s.arg.GenerateRandomPassphrase {
   120  			return SecretStoreNotFunctionalError{err: fmt.Errorf("secret store is required for no-passphrase signup but wasn't found")}
   121  		} else {
   122  			m.Debug("There is no secret store, but we are continuing because this is not a NOPW")
   123  		}
   124  	}
   125  
   126  	m = m.WithNewProvisionalLoginContext()
   127  
   128  	if err = s.genPassphraseStream(m, s.arg.Passphrase, s.arg.GenerateRandomPassphrase); err != nil {
   129  		return err
   130  	}
   131  
   132  	if s.arg.BotToken.Exists() && s.arg.InviteCode == "" {
   133  		s.arg.InviteCode, err = libkb.GetInvitationCode(m)
   134  		if err != nil {
   135  			return err
   136  		}
   137  	}
   138  
   139  	if err = s.join(m, *s.arg); err != nil {
   140  		return err
   141  	}
   142  
   143  	s.perUserKeyring, err = libkb.NewPerUserKeyring(m.G(), s.uid)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	err = s.registerDevice(m, s.arg.DeviceName, s.arg.GenerateRandomPassphrase)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	if s.arg.BotToken.IsNil() {
   154  		m.Info("Signed up and provisioned a device.")
   155  	}
   156  
   157  	// After we are provisioned, do not fail the signup process. Everything
   158  	// else happening here is optional.
   159  
   160  	if !s.arg.SkipPaper {
   161  		if err = s.genPaperKeys(m); err != nil {
   162  			m.Warning("Paper key was not generated. Failed with an error: %s", err)
   163  		}
   164  	}
   165  
   166  	// GenPGPBatch can be set in devel CLI to generate
   167  	// a pgp key and push it to the server without any
   168  	// user interaction to make testing easier.
   169  	if s.arg.GenPGPBatch {
   170  		if err = s.genPGPBatch(m); err != nil {
   171  			m.Warning("genPGPBatch failed with an error: %s", err)
   172  		}
   173  	}
   174  
   175  	if err := s.doGPG(m); err != nil {
   176  		// We don't care if GPG import fails, continue with the signup process
   177  		// because it's too late anyway. Failing here would leave a signed up
   178  		// and logged in user in a weird state where their GUI does not know
   179  		// they are logged in, and also other processes (CreateWallet) will not
   180  		// run.
   181  		m.Warning("Attempt at importing PGP keys from GPG failed with: %s", err)
   182  	}
   183  
   184  	m = m.CommitProvisionalLogin()
   185  
   186  	// signup complete, notify anyone interested.
   187  	m.G().NotifyRouter.HandleSignup(m.Ctx(), s.arg.Username)
   188  
   189  	// For instance, setup gregor and friends...
   190  	m.G().CallLoginHooks(m)
   191  
   192  	m.G().GetStellar().CreateWalletSoft(m.Ctx())
   193  
   194  	return nil
   195  }
   196  
   197  func (s *SignupEngine) doGPG(m libkb.MetaContext) error {
   198  
   199  	if s.arg.SkipGPG {
   200  		return nil
   201  	}
   202  
   203  	// only desktop potentially has gpg, so if not desktop then
   204  	// bail out
   205  	if s.arg.DeviceType != keybase1.DeviceType_DESKTOP {
   206  		return nil
   207  	}
   208  
   209  	if wantsGPG, err := s.checkGPG(m); err != nil {
   210  		return err
   211  	} else if wantsGPG {
   212  		if err := s.addGPG(m, true, true); err != nil {
   213  			return fmt.Errorf("addGPG error: %s", err)
   214  		}
   215  	}
   216  	return nil
   217  }
   218  
   219  func (s *SignupEngine) genRandomPassphrase(m libkb.MetaContext) (string, error) {
   220  	str, err := libkb.RandBytes(randomPassphraseLen)
   221  	if err != nil {
   222  		return "", err
   223  	}
   224  	return base64.StdEncoding.EncodeToString(str), nil
   225  }
   226  
   227  func (s *SignupEngine) genPassphraseStream(m libkb.MetaContext, passphrase string, randomPW bool) error {
   228  	if randomPW {
   229  		if len(passphrase) != 0 {
   230  			return fmt.Errorf("Tried to generate random passphrase but also provided passphrase argument")
   231  		}
   232  		var err error
   233  		passphrase, err = s.genRandomPassphrase(m)
   234  		if err != nil {
   235  			return err
   236  		}
   237  	}
   238  	if len(passphrase) < libkb.MinPassphraseLength {
   239  		return libkb.PassphraseError{Msg: fmt.Sprintf("Passphrase must be at least %d characters", libkb.MinPassphraseLength)}
   240  	}
   241  	salt, err := libkb.RandBytes(triplesec.SaltLen)
   242  	if err != nil {
   243  		return err
   244  	}
   245  	s.pwsalt = salt
   246  	s.tsec, s.ppStream, err = libkb.StretchPassphrase(m.G(), passphrase, salt)
   247  	if err != nil {
   248  		return err
   249  	}
   250  	return nil
   251  }
   252  
   253  func (s *SignupEngine) join(m libkb.MetaContext, arg SignupEngineRunArg) error {
   254  	m.Debug("SignupEngine#join")
   255  	joinEngine := NewSignupJoinEngine(m.G())
   256  
   257  	pdpkda5kid, err := s.ppStream.PDPKA5KID()
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	jarg := SignupJoinEngineRunArg{
   263  		Username:    arg.Username,
   264  		Email:       arg.Email,
   265  		InviteCode:  arg.InviteCode,
   266  		PWHash:      s.ppStream.PWHash(),
   267  		PWSalt:      s.pwsalt,
   268  		RandomPW:    arg.GenerateRandomPassphrase,
   269  		SkipMail:    arg.SkipMail,
   270  		PDPKA5KID:   pdpkda5kid,
   271  		VerifyEmail: arg.VerifyEmail,
   272  		BotToken:    arg.BotToken,
   273  	}
   274  	res := joinEngine.Run(m, jarg)
   275  	if res.Err != nil {
   276  		return res
   277  	}
   278  
   279  	s.ppStream.SetGeneration(res.PpGen)
   280  	m.LoginContext().CreateStreamCache(s.tsec, s.ppStream)
   281  
   282  	s.uid = res.UV.Uid
   283  	luArg := libkb.NewLoadUserArgWithMetaContext(m).WithSelf(true).WithUID(res.UV.Uid).WithPublicKeyOptional()
   284  	user, err := libkb.LoadUser(luArg)
   285  	if err != nil {
   286  		return err
   287  	}
   288  
   289  	s.me = user
   290  	return nil
   291  }
   292  
   293  func (s *SignupEngine) generateEldestPaperKey(m libkb.MetaContext, args *DeviceWrapArgs) (err error) {
   294  	tmp, err := libkb.MakePaperKeyPhrase(libkb.PaperKeyVersion)
   295  	if err != nil {
   296  		return err
   297  	}
   298  	s.paperKey = &tmp
   299  
   300  	kgarg := &PaperKeyGenArg{
   301  		Passphrase: tmp,
   302  		Me:         s.me,
   303  		SkipPush:   true,
   304  	}
   305  	eng := NewPaperKeyGen(m.G(), kgarg)
   306  	err = RunEngine2(m, eng)
   307  	if err != nil {
   308  		return err
   309  	}
   310  	args.naclSigningKeyPair = eng.SigKey().(libkb.NaclKeyPair)
   311  	args.naclEncryptionKeyPair = eng.EncKey()
   312  	args.DeviceName = s.paperKey.Prefix()
   313  	args.DeviceType = keybase1.DeviceTypeV2_PAPER
   314  	args.DeviceID = eng.DeviceID()
   315  
   316  	return nil
   317  }
   318  
   319  func (s *SignupEngine) registerDevice(m libkb.MetaContext, deviceName string, randomPw bool) error {
   320  	m.Debug("SignupEngine#registerDevice")
   321  	s.lks = libkb.NewLKSec(s.ppStream, s.uid)
   322  	args := &DeviceWrapArgs{
   323  		Me:                    s.me,
   324  		DeviceName:            libkb.CheckDeviceName.Transform(deviceName),
   325  		Lks:                   s.lks,
   326  		IsEldest:              true,
   327  		naclSigningKeyPair:    s.arg.naclSigningKeyPair,
   328  		naclEncryptionKeyPair: s.arg.naclEncryptionKeyPair,
   329  	}
   330  
   331  	switch s.arg.DeviceType {
   332  	case keybase1.DeviceType_DESKTOP:
   333  		args.DeviceType = keybase1.DeviceTypeV2_DESKTOP
   334  	case keybase1.DeviceType_MOBILE:
   335  		args.DeviceType = keybase1.DeviceTypeV2_MOBILE
   336  	default:
   337  		if s.arg.BotToken.IsNil() {
   338  			return fmt.Errorf("unknown device type: %v", s.arg.DeviceType)
   339  		}
   340  	}
   341  
   342  	if s.arg.BotToken.Exists() {
   343  		err := s.generateEldestPaperKey(m, args)
   344  		if err != nil {
   345  			return err
   346  		}
   347  	}
   348  
   349  	if !libkb.CheckDeviceName.F(args.DeviceName) {
   350  		m.Debug("invalid device name supplied: %s", args.DeviceName)
   351  		return libkb.DeviceBadNameError{}
   352  	}
   353  
   354  	eng := NewDeviceWrap(m.G(), args)
   355  	err := RunEngine2(m, eng)
   356  	if err != nil {
   357  		m.Warning("Failed to provision device: %s", err)
   358  		if ssErr := s.storeSecretForRecovery(m); ssErr != nil {
   359  			m.Warning("Failed to store secrets for recovery: %s", ssErr)
   360  		}
   361  		return err
   362  	}
   363  
   364  	if err := eng.SwitchConfigAndActiveDevice(m); err != nil {
   365  		return err
   366  	}
   367  
   368  	s.signingKey = eng.SigningKey()
   369  	s.encryptionKey = eng.EncryptionKey()
   370  	did := eng.DeviceID()
   371  
   372  	if err := m.LoginContext().LocalSession().SetDeviceProvisioned(did); err != nil {
   373  		// this isn't a fatal error, session will stay in memory...
   374  		m.Warning("error saving session file: %s", err)
   375  	}
   376  
   377  	s.storeSecret(m, randomPw)
   378  
   379  	m.Debug("registered new device: %s", m.G().Env.GetDeviceID())
   380  	m.Debug("eldest kid: %s", s.me.GetEldestKID())
   381  
   382  	return nil
   383  }
   384  
   385  func (s *SignupEngine) storeSecret(m libkb.MetaContext, randomPw bool) {
   386  	defer m.Trace("SignupEngine#storeSecret", nil)()
   387  
   388  	// Create the secret store as late as possible here, as the username may
   389  	// change during the signup process.
   390  	if !s.arg.StoreSecret {
   391  		m.Debug("not storing secret; disabled")
   392  		return
   393  	}
   394  
   395  	w := libkb.StoreSecretAfterLoginWithLKSWithOptions(m, s.me.GetNormalizedName(), s.lks, &libkb.SecretStoreOptions{RandomPw: randomPw})
   396  	if w != nil {
   397  		m.Warning("StoreSecret error: %s", w)
   398  	}
   399  }
   400  
   401  func (s *SignupEngine) storeSecretForRecovery(m libkb.MetaContext) (err error) {
   402  	defer m.Trace("SignupEngine#storeSecretForRecovery", &err)()
   403  
   404  	if !s.arg.GenerateRandomPassphrase {
   405  		m.Debug("Not GenerateRandomPassphrase - skipping storeSecretForRecovery")
   406  		return nil
   407  	}
   408  
   409  	username := s.me.GetNormalizedName()
   410  	err = libkb.StorePwhashEddsaPassphraseStream(m, username, s.ppStream)
   411  	if err != nil {
   412  		return err
   413  	}
   414  
   415  	return nil
   416  }
   417  
   418  func (s *SignupEngine) genPaperKeys(m libkb.MetaContext) error {
   419  	m.Debug("SignupEngine#genPaperKeys")
   420  	// Load me again so that keys will be up to date.
   421  	var err error
   422  	s.me, err = libkb.LoadUser(libkb.NewLoadUserArgWithMetaContext(m).WithSelf(true).WithUID(s.me.GetUID()).WithPublicKeyOptional())
   423  	if err != nil {
   424  		return err
   425  	}
   426  
   427  	args := &PaperKeyPrimaryArgs{
   428  		Me:             s.me,
   429  		SigningKey:     s.signingKey,
   430  		EncryptionKey:  s.encryptionKey,
   431  		PerUserKeyring: s.perUserKeyring,
   432  	}
   433  
   434  	eng := NewPaperKeyPrimary(m.G(), args)
   435  	return RunEngine2(m, eng)
   436  }
   437  
   438  func (s *SignupEngine) checkGPG(m libkb.MetaContext) (bool, error) {
   439  	eng := NewGPGImportKeyEngine(m.G(), nil)
   440  	return eng.WantsGPG(m)
   441  }
   442  
   443  func (s *SignupEngine) addGPG(m libkb.MetaContext, allowMulti bool, hasProvisionedDevice bool) (err error) {
   444  	defer m.Trace(fmt.Sprintf("SignupEngine.addGPG(signingKey: %v)", s.signingKey), &err)()
   445  
   446  	arg := GPGImportKeyArg{Signer: s.signingKey, AllowMulti: allowMulti, Me: s.me, Lks: s.lks, HasProvisionedDevice: hasProvisionedDevice}
   447  	eng := NewGPGImportKeyEngine(m.G(), &arg)
   448  	if err = RunEngine2(m, eng); err != nil {
   449  		return err
   450  	}
   451  
   452  	if s.signingKey == nil {
   453  		s.signingKey = eng.LastKey()
   454  	}
   455  	return nil
   456  }
   457  
   458  func (s *SignupEngine) genPGPBatch(m libkb.MetaContext) error {
   459  	m.Debug("SignupEngine#genPGPBatch")
   460  	gen := libkb.PGPGenArg{
   461  		PrimaryBits: 1024,
   462  		SubkeyBits:  1024,
   463  	}
   464  
   465  	// genPGPBatch should never be run in production, but if there's
   466  	// a bug or a mistunderstanding in the future, generate a good key.
   467  	if m.G().Env.GetRunMode() != libkb.DevelRunMode {
   468  		gen.PrimaryBits = 4096
   469  		gen.SubkeyBits = 4096
   470  	}
   471  	gen.AddDefaultUID(m.G())
   472  
   473  	tsec, sgen := m.LoginContext().PassphraseStreamCache().TriplesecAndGeneration()
   474  
   475  	eng := NewPGPKeyImportEngine(m.G(), PGPKeyImportEngineArg{
   476  		Gen:              &gen,
   477  		PushSecret:       true,
   478  		Lks:              s.lks,
   479  		NoSave:           true,
   480  		PreloadTsec:      tsec,
   481  		PreloadStreamGen: sgen,
   482  	})
   483  
   484  	return RunEngine2(m, eng)
   485  }