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 }