github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/device_add.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 "golang.org/x/net/context" 8 9 "github.com/keybase/client/go/kex2" 10 "github.com/keybase/client/go/libkb" 11 keybase1 "github.com/keybase/client/go/protocol/keybase1" 12 ) 13 14 // DeviceAdd is an engine. 15 type DeviceAdd struct { 16 libkb.Contextified 17 } 18 19 // NewDeviceAdd creates a DeviceAdd engine. 20 func NewDeviceAdd(g *libkb.GlobalContext) *DeviceAdd { 21 return &DeviceAdd{ 22 Contextified: libkb.NewContextified(g), 23 } 24 } 25 26 // Name is the unique engine name. 27 func (e *DeviceAdd) Name() string { 28 return "DeviceAdd" 29 } 30 31 // GetPrereqs returns the engine prereqs. 32 func (e *DeviceAdd) Prereqs() Prereqs { 33 return Prereqs{Device: true} 34 } 35 36 // RequiredUIs returns the required UIs. 37 func (e *DeviceAdd) RequiredUIs() []libkb.UIKind { 38 return []libkb.UIKind{libkb.ProvisionUIKind} 39 } 40 41 // SubConsumers returns the other UI consumers for this engine. 42 func (e *DeviceAdd) SubConsumers() []libkb.UIConsumer { 43 return []libkb.UIConsumer{ 44 &Kex2Provisioner{}, 45 } 46 } 47 48 func (e *DeviceAdd) promptLoop(m libkb.MetaContext, provisioner *Kex2Provisioner, secret *libkb.Kex2Secret, provisioneeType keybase1.DeviceType) (err error) { 49 sb := secret.Secret() 50 arg := keybase1.DisplayAndPromptSecretArg{ 51 Secret: sb[:], 52 Phrase: secret.Phrase(), 53 OtherDeviceType: provisioneeType, 54 } 55 for i := 0; i < 10; i++ { 56 receivedSecret, err := m.UIs().ProvisionUI.DisplayAndPromptSecret(m.Ctx(), arg) 57 if err != nil { 58 m.Warning("DisplayAndPromptSecret error: %s", err) 59 return err 60 } 61 62 if receivedSecret.Secret != nil && len(receivedSecret.Secret) > 0 { 63 m.Debug("received secret, adding to provisioner") 64 var ks kex2.Secret 65 copy(ks[:], receivedSecret.Secret) 66 provisioner.AddSecret(ks) 67 return nil 68 } 69 70 if len(receivedSecret.Phrase) > 0 { 71 m.Debug("received secret phrase, checking validity") 72 checker := libkb.MakeCheckKex2SecretPhrase(m.G()) 73 if !checker.F(receivedSecret.Phrase) { 74 m.Debug("secret phrase failed validity check (attempt %d)", i+1) 75 arg.PreviousErr = checker.Hint 76 continue 77 } 78 uid := m.CurrentUID() 79 m.Debug("received secret phrase, adding to provisioner with uid=%s", uid) 80 ks, err := libkb.NewKex2SecretFromUIDAndPhrase(uid, receivedSecret.Phrase) 81 if err != nil { 82 m.Warning("NewKex2SecretFromPhrase error: %s", err) 83 return err 84 } 85 provisioner.AddSecret(ks.Secret()) 86 return nil 87 } 88 89 if provisioneeType == keybase1.DeviceType_MOBILE { 90 // for mobile provisionee, only displaying the secret so it's 91 // ok/expected that nothing came back 92 m.Debug("device add DisplayAndPromptSecret returned empty secret, stopping retry loop") 93 return nil 94 } 95 } 96 97 return libkb.RetryExhaustedError{} 98 } 99 100 // Run starts the engine. 101 func (e *DeviceAdd) Run(m libkb.MetaContext) (err error) { 102 defer m.Trace("DeviceAdd#Run", &err)() 103 104 m.G().LocalSigchainGuard().Set(m.Ctx(), "DeviceAdd") 105 defer m.G().LocalSigchainGuard().Clear(m.Ctx(), "DeviceAdd") 106 107 arg := keybase1.ChooseDeviceTypeArg{Kind: keybase1.ChooseType_NEW_DEVICE} 108 provisioneeType, err := m.UIs().ProvisionUI.ChooseDeviceType(context.TODO(), arg) 109 if err != nil { 110 return err 111 } 112 uid := m.CurrentUID() 113 114 // make a new secret; continue to generate legacy Kex2 secrets for now. 115 kex2SecretTyp := libkb.Kex2SecretTypeV1Desktop 116 if provisioneeType == keybase1.DeviceType_MOBILE || m.G().IsMobileAppType() { 117 kex2SecretTyp = libkb.Kex2SecretTypeV1Mobile 118 } 119 m.Debug("provisionee device type: %v; uid: %s; secret type: %d", provisioneeType, uid, kex2SecretTyp) 120 secret, err := libkb.NewKex2SecretFromTypeAndUID(kex2SecretTyp, uid) 121 if err != nil { 122 return err 123 } 124 m.Debug("secret phrase received") 125 126 // provisioner needs ppstream, and UI is confusing when it asks for 127 // it at the same time as asking for the secret, so get it first 128 // before prompting for the kex2 secret: 129 pps, err := libkb.GetPassphraseStreamStored(m) 130 if err != nil { 131 return err 132 } 133 134 // create provisioner engine 135 provisioner := NewKex2Provisioner(m.G(), secret.Secret(), pps) 136 137 var canceler func() 138 m, canceler = m.WithContextCancel() 139 140 // display secret and prompt for secret from X in a goroutine: 141 go func() { 142 err := e.promptLoop(m, provisioner, secret, provisioneeType) 143 if err != nil { 144 m.Debug("DeviceAdd prompt loop error: %s", err) 145 canceler() 146 } 147 }() 148 149 defer func() { 150 canceler() 151 }() 152 153 if err := RunEngine2(m, provisioner); err != nil { 154 if err == kex2.ErrHelloTimeout { 155 err = libkb.CanceledError{M: "Failed to provision device: are you sure you typed the secret properly?"} 156 } 157 return err 158 } 159 160 m.G().KeyfamilyChanged(m.Ctx(), m.G().Env.GetUID()) 161 162 return nil 163 }