/
bootstrap_active_device.go
214 lines (188 loc) · 7.11 KB
/
bootstrap_active_device.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
package libkb
import (
"fmt"
"github.com/keybase/client/go/protocol/keybase1"
)
type LoadUnlockedDeviceKeysMode int
const (
// Normal checks the cache and polls if it is stale. StaleOK checks the cache
// and polls if it is empty. Offline uses the cache and errors if it is empty.
LoadUnlockedDeviceKeysModeNormal LoadUnlockedDeviceKeysMode = iota
LoadUnlockedDeviceKeysModeStaleOK
LoadUnlockedDeviceKeysModeOffline
)
func loadAndUnlockKey(m MetaContext, kr *SKBKeyringFile, secretStore SecretStore, uid keybase1.UID, kid keybase1.KID) (key GenericKey, err error) {
defer m.Trace(fmt.Sprintf("loadAndUnlockKey(%s)", kid), &err)()
locked := kr.LookupByKid(kid)
if locked == nil {
m.Debug("loadAndUnlockKey: no locked key for %s", kid)
return nil, NoKeyError{fmt.Sprintf("no key for %s", kid)}
}
locked.SetUID(uid)
unlocked, err := locked.UnlockNoPrompt(m, secretStore)
if err != nil {
m.Debug("Failed to unlock key %s: %s", kid, err)
return nil, err
}
return unlocked, err
}
// BootstrapActiveDeviceFromConfig takes the user's config.json, keys.mpack file and
// secret store to populate ActiveDevice, and to have all credentials necessary
// to sign NIST tokens, allowing the user to act as if "logged in". Will return
// nil if everything work, LoginRequiredError if a real "login" is required to
// make the app work, and various errors on unexpected failures.
func BootstrapActiveDeviceFromConfig(m MetaContext, online bool) (uid keybase1.UID, err error) {
uid, err = bootstrapActiveDeviceFromConfigReturnRawError(m, online, keybase1.UID(""))
err = fixupBootstrapError(err)
return uid, err
}
func bootstrapActiveDeviceFromConfigReturnRawError(m MetaContext, online bool, assertUID keybase1.UID) (uid keybase1.UID, err error) {
uid = m.G().Env.GetUID()
if uid.IsNil() {
return uid, NoUIDError{}
}
if assertUID.Exists() && !assertUID.Equal(uid) {
return uid, NewUIDMismatchError(fmt.Sprintf("wanted %s but got %s", assertUID, uid))
}
deviceID := m.G().Env.GetDeviceIDForUID(uid)
if deviceID.IsNil() {
return uid, NoDeviceError{fmt.Sprintf("no device in config for UID=%s", uid)}
}
err = bootstrapActiveDeviceReturnRawError(m, uid, deviceID, online)
return uid, err
}
func isBootstrapLoggedOutError(err error) bool {
if _, ok := err.(NoUIDError); ok {
return true
}
if err == ErrUnlockNotPossible {
return true
}
return false
}
func fixupBootstrapError(err error) error {
if err == nil {
return nil
}
if isBootstrapLoggedOutError(err) {
return LoginRequiredError{err.Error()}
}
return err
}
func bootstrapActiveDeviceReturnRawError(m MetaContext, uid keybase1.UID, deviceID keybase1.DeviceID, online bool) (err error) {
defer m.Trace(fmt.Sprintf("bootstrapActiveDeviceReturnRaw(%s,%s)", uid, deviceID), &err)()
ad := m.ActiveDevice()
if ad.IsValidFor(uid, deviceID) {
m.Debug("active device is current")
return nil
}
mode := LoadUnlockedDeviceKeysModeNormal
if !online {
mode = LoadUnlockedDeviceKeysModeOffline
}
uv, sib, sub, deviceName, err := LoadUnlockedDeviceKeys(m, uid, deviceID, mode)
if err != nil {
return err
}
err = m.SetActiveDevice(uv, deviceID, sib, sub, deviceName, KeychainModeOS)
return err
}
func LoadUnlockedDeviceKeys(m MetaContext, uid keybase1.UID, deviceID keybase1.DeviceID, mode LoadUnlockedDeviceKeysMode) (uv keybase1.UserVersion, sib GenericKey, sub GenericKey, deviceName string, err error) {
defer m.Trace(fmt.Sprintf("LoadUnlockedDeviceKeys(uid=%q)", uid), &err)()
// use the UPAKLoader with StaleOK, CachedOnly in order to get cached upak
arg := NewLoadUserArgWithMetaContext(m).WithUID(uid).WithPublicKeyOptional()
if mode == LoadUnlockedDeviceKeysModeOffline {
arg = arg.WithStaleOK(true).WithCachedOnly(true)
} else if mode == LoadUnlockedDeviceKeysModeStaleOK {
arg = arg.WithStaleOK(true)
}
upak, _, err := m.G().GetUPAKLoader().LoadV2(arg)
if err != nil {
m.Debug("BootstrapActiveDevice: upak.Load err: %s", err)
return uv, nil, nil, deviceName, err
}
if upak.Current.Status == keybase1.StatusCode_SCDeleted {
m.Debug("User %s was deleted", uid)
return uv, nil, nil, deviceName, UserDeletedError{}
}
device := upak.Current.FindSigningDeviceKey(deviceID)
if device == nil {
m.Debug("BootstrapActiveDevice: no sibkey found for device %s", deviceID)
return uv, nil, nil, deviceName, NoKeyError{"no signing device key found for user"}
}
if device.Base.Revocation != nil {
m.Debug("BootstrapActiveDevice: device %s was revoked", deviceID)
return uv, nil, nil, deviceName, NewKeyRevokedError("active device")
}
sibkeyKID := device.Base.Kid
deviceName = device.DeviceDescription
subkeyKID := upak.Current.FindEncryptionKIDFromSigningKID(sibkeyKID)
if subkeyKID.IsNil() {
m.Debug("BootstrapActiveDevice: no subkey found for device: %s", deviceID)
return uv, nil, nil, deviceName, NoKeyError{"no encryption device key found for user"}
}
// load the keyring file
username := NewNormalizedUsername(upak.Current.Username)
kr, err := LoadSKBKeyring(m, username)
if err != nil {
m.Debug("BootstrapActiveDevice: error loading keyring for %s: %s", username, err)
return uv, nil, nil, deviceName, err
}
secretStore := NewSecretStore(m, username)
sib, err = loadAndUnlockKey(m, kr, secretStore, uid, sibkeyKID)
if err != nil {
return uv, nil, nil, deviceName, err
}
sub, err = loadAndUnlockKey(m, kr, secretStore, uid, subkeyKID)
if err != nil {
return uv, nil, nil, deviceName, err
}
return upak.Current.ToUserVersion(), sib, sub, deviceName, nil
}
func LoadProvisionalActiveDevice(m MetaContext, uid keybase1.UID, deviceID keybase1.DeviceID, online bool) (ret *ActiveDevice, err error) {
defer m.Trace("LoadProvisionalActiveDevice", &err)()
mode := LoadUnlockedDeviceKeysModeStaleOK
if !online {
mode = LoadUnlockedDeviceKeysModeOffline
}
uv, sib, sub, deviceName, err := LoadUnlockedDeviceKeys(m, uid, deviceID, mode)
if err != nil {
return nil, err
}
ret = NewProvisionalActiveDevice(m, uv, deviceID, sib, sub, deviceName, KeychainModeOS)
return ret, nil
}
// BootstrapActiveDeviceWithMetaContext will setup an ActiveDevice with a NIST Factory
// for the caller. The m.loginContext passed through isn't really needed
// for anything aside from assertions, but as we phase out LoginState, we'll
// leave it here so that assertions in LoginState can still pass.
func BootstrapActiveDeviceWithMetaContext(m MetaContext) (ok bool, uid keybase1.UID, err error) {
uid, err = BootstrapActiveDeviceFromConfig(m, true)
ok = false
if err == nil {
ok = true
} else if _, isLRE := err.(LoginRequiredError); isLRE {
err = nil
}
return ok, uid, err
}
// BootstrapActiveDeviceWithMetaContextAndAssertUID will setup an ActiveDevice with a NIST Factory
// for the caller. It only works if we're logged in as the given UID
func BootstrapActiveDeviceWithMetaContextAndAssertUID(m MetaContext, uid keybase1.UID) (ok bool, err error) {
_, err = bootstrapActiveDeviceFromConfigReturnRawError(m, true, uid)
switch err.(type) {
case nil:
return true, nil
case LoginRequiredError:
return false, nil
case UIDMismatchError:
return false, nil
case NoUIDError:
return false, nil
default:
if err == ErrUnlockNotPossible {
return false, nil
}
return false, err
}
}