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 }