github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }