github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/device_history.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  	"errors"
     8  	"fmt"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/kbcrypto"
    12  	"github.com/keybase/client/go/libkb"
    13  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    14  )
    15  
    16  // DeviceHistory is an engine.
    17  type DeviceHistory struct {
    18  	libkb.Contextified
    19  	username string
    20  	devices  []keybase1.DeviceDetail
    21  }
    22  
    23  // NewDeviceHistory creates a DeviceHistory engine to lookup the
    24  // device history for username.
    25  func NewDeviceHistory(g *libkb.GlobalContext, username string) *DeviceHistory {
    26  	return &DeviceHistory{
    27  		Contextified: libkb.NewContextified(g),
    28  		username:     username,
    29  	}
    30  }
    31  
    32  // NewDeviceHistorySelf creates a DeviceHistory engine to lookup
    33  // the device history of the current user.
    34  func NewDeviceHistorySelf(g *libkb.GlobalContext) *DeviceHistory {
    35  	return &DeviceHistory{
    36  		Contextified: libkb.NewContextified(g),
    37  	}
    38  }
    39  
    40  // Name is the unique engine name.
    41  func (e *DeviceHistory) Name() string {
    42  	return "DeviceHistory"
    43  }
    44  
    45  // GetPrereqs returns the engine prereqs.
    46  func (e *DeviceHistory) Prereqs() Prereqs {
    47  	if len(e.username) > 0 {
    48  		return Prereqs{}
    49  	}
    50  	return Prereqs{Device: true}
    51  }
    52  
    53  // RequiredUIs returns the required UIs.
    54  func (e *DeviceHistory) RequiredUIs() []libkb.UIKind {
    55  	return []libkb.UIKind{}
    56  }
    57  
    58  // SubConsumers returns the other UI consumers for this engine.
    59  func (e *DeviceHistory) SubConsumers() []libkb.UIConsumer {
    60  	return nil
    61  }
    62  
    63  // Run starts the engine.
    64  func (e *DeviceHistory) Run(m libkb.MetaContext) error {
    65  
    66  	arg := e.loadUserArg(m)
    67  	err := m.G().GetFullSelfer().WithUser(arg, func(u *libkb.User) error {
    68  		return e.loadDevices(m, u)
    69  	})
    70  	return err
    71  }
    72  
    73  func (e *DeviceHistory) Devices() []keybase1.DeviceDetail {
    74  	return e.devices
    75  }
    76  
    77  func (e *DeviceHistory) loadUserArg(m libkb.MetaContext) libkb.LoadUserArg {
    78  	arg := libkb.NewLoadUserPubOptionalArg(m.G())
    79  	if len(e.username) == 0 {
    80  		arg = arg.WithSelf(true)
    81  	} else {
    82  		arg = arg.WithName(e.username)
    83  	}
    84  	return arg
    85  }
    86  
    87  func (e *DeviceHistory) loadDevices(m libkb.MetaContext, user *libkb.User) error {
    88  	ckf := user.GetComputedKeyFamily()
    89  	if ckf == nil {
    90  		return errors.New("nil ComputedKeyFamily for user")
    91  	}
    92  	ckis := user.GetComputedKeyInfos()
    93  	if ckis == nil {
    94  		return errors.New("nil ComputedKeyInfos for user")
    95  	}
    96  
    97  	for _, d := range ckf.GetAllDevices() {
    98  		exp := keybase1.DeviceDetail{Device: *(d.ProtExportWithDeviceNum())}
    99  		cki, ok := ckis.Infos[d.Kid]
   100  		if !ok {
   101  			return fmt.Errorf("no ComputedKeyInfo for device %s, kid %s", d.ID, d.Kid)
   102  		}
   103  
   104  		if cki.Eldest {
   105  			exp.Eldest = true
   106  		} else {
   107  			prov, err := e.provisioner(m, d, ckis, cki)
   108  			if err != nil {
   109  				return err
   110  			}
   111  			if prov != nil {
   112  				exp.Provisioner = prov.ProtExport()
   113  				t := keybase1.TimeFromSeconds(cki.DelegatedAt.Unix)
   114  				exp.ProvisionedAt = &t
   115  			}
   116  		}
   117  
   118  		if cki.RevokedAt != nil {
   119  			rt := keybase1.TimeFromSeconds(cki.RevokedAt.Unix)
   120  			exp.RevokedAt = &rt
   121  		}
   122  		if !cki.RevokedBy.IsNil() {
   123  			exp.RevokedBy = cki.RevokedBy
   124  			if deviceID, ok := ckis.KIDToDeviceID[cki.RevokedBy]; ok {
   125  				if device, ok := ckis.Devices[deviceID]; ok {
   126  					exp.RevokedByDevice = device.ProtExport()
   127  				}
   128  			}
   129  		}
   130  
   131  		if m.G().Env.GetDeviceIDForUsername(user.GetNormalizedName()).Eq(d.ID) {
   132  			exp.CurrentDevice = true
   133  		}
   134  
   135  		e.devices = append(e.devices, exp)
   136  	}
   137  
   138  	// Load the last used times, but only if these are your own devices. The
   139  	// API won't give you those times for other people's devices.
   140  	if user.GetNormalizedName().Eq(m.G().Env.GetUsername()) {
   141  		lastUsedTimes, err := e.getLastUsedTimes(m)
   142  		if err != nil {
   143  			return err
   144  		}
   145  		for i := range e.devices {
   146  			detail := &e.devices[i]
   147  			lastUsedTime, ok := lastUsedTimes[detail.Device.DeviceID]
   148  			if !ok {
   149  				if detail.RevokedAt != nil {
   150  					// The server only provides last used times for active devices.
   151  					continue
   152  				}
   153  				return fmt.Errorf("Failed to load last used time for device %s", detail.Device.DeviceID)
   154  			}
   155  			detail.Device.LastUsedTime = keybase1.TimeFromSeconds(lastUsedTime.Unix())
   156  		}
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  func (e *DeviceHistory) provisioner(m libkb.MetaContext, d libkb.DeviceWithDeviceNumber, ckis *libkb.ComputedKeyInfos, info *libkb.ComputedKeyInfo) (*libkb.Device, error) {
   163  	for _, v := range info.Delegations {
   164  		if kbcrypto.AlgoType(v.GetKeyType()) != kbcrypto.KIDNaclEddsa {
   165  			// only concerned with device history, not pgp provisioners
   166  			continue
   167  		}
   168  
   169  		did, ok := ckis.KIDToDeviceID[v]
   170  		if !ok {
   171  			return nil, fmt.Errorf("device %s provisioned by kid %s, but couldn't find matching device ID in ComputedKeyInfos", d.ID, v)
   172  		}
   173  		prov, ok := ckis.Devices[did]
   174  		if !ok {
   175  			return nil, fmt.Errorf("device %s provisioned by device %s, but couldn't find matching device in ComputedKeyInfos", d.ID, did)
   176  		}
   177  		return prov, nil
   178  	}
   179  
   180  	return nil, nil
   181  }
   182  
   183  func (e *DeviceHistory) getLastUsedTimes(m libkb.MetaContext) (ret map[keybase1.DeviceID]time.Time, err error) {
   184  	defer m.Trace("DeviceHistory#getLastUsedTimes", &err)()
   185  	var devs libkb.DeviceKeyMap
   186  	var ss *libkb.SecretSyncer
   187  	ss, err = m.ActiveDevice().SyncSecretsForce(m)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	devs, err = ss.ActiveDevices(libkb.AllDeviceTypes)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	ret = map[keybase1.DeviceID]time.Time{}
   196  	for deviceID, dev := range devs {
   197  		ret[deviceID] = time.Unix(dev.LastUsedTime, 0)
   198  	}
   199  	return ret, nil
   200  }