-
Notifications
You must be signed in to change notification settings - Fork 0
/
errors.go
256 lines (228 loc) · 8.43 KB
/
errors.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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package ephemeral
import (
"fmt"
"strings"
"time"
"github.com/adamwalz/keybase-client/go/libkb"
"github.com/adamwalz/keybase-client/go/protocol/gregor1"
"github.com/adamwalz/keybase-client/go/protocol/keybase1"
)
type EphemeralKeyKind string
const (
DeviceEKKind EphemeralKeyKind = "deviceEK"
UserEKKind EphemeralKeyKind = "userEK"
TeamEKKind EphemeralKeyKind = "teamEK"
TeambotEKKind EphemeralKeyKind = "teambotEK"
)
type EphemeralKeyErrorKind int
const (
EphemeralKeyErrorKindDEVICENOTAUTHENTICATED EphemeralKeyErrorKind = iota
EphemeralKeyErrorKindUNBOX
EphemeralKeyErrorKindMISSINGBOX
EphemeralKeyErrorKindWRONGKID
EphemeralKeyErrorKindCORRUPTEDGEN
EphemeralKeyErrorKindDEVICEAFTEREK
EphemeralKeyErrorKindMEMBERAFTEREK
EphemeralKeyErrorKindDEVICESTALE
EphemeralKeyErrorKindUSERSTALE
EphemeralKeyErrorKindUNKNOWN
)
type EphemeralKeyError struct {
DebugMsg string
HumanMsg string
StatusCode int
Ctime gregor1.Time
ErrKind EphemeralKeyErrorKind
EKKind EphemeralKeyKind
IsTransient bool
}
func (e EphemeralKeyError) HumanError() string {
return e.HumanMsg
}
func (e EphemeralKeyError) Error() string {
return e.DebugMsg
}
// AllowTransient determines if we allow the given error to be downgraded to a
// transient error. If we encounter a MISSINGBOX error for a TeambotEK we
// allow this to be marked as transient for a 24 hour window. The intention is
// to allow a chat message to be retried on send for this period instead of
// permanently failing.
func (e EphemeralKeyError) AllowTransient() bool {
return (e.EKKind == TeambotEKKind &&
e.ErrKind == EphemeralKeyErrorKindMISSINGBOX &&
time.Since(e.Ctime.Time()) < time.Hour*24)
}
func (e EphemeralKeyError) IsPermanent() bool {
return !e.IsTransient
}
func newTransientEphemeralKeyError(err EphemeralKeyError) EphemeralKeyError {
return EphemeralKeyError{
DebugMsg: err.DebugMsg,
HumanMsg: err.HumanMsg,
StatusCode: err.StatusCode,
Ctime: err.Ctime,
ErrKind: err.ErrKind,
EKKind: err.EKKind,
IsTransient: true,
}
}
const (
DefaultHumanErrMsg = "This exploding message is not available"
DefaultPluralHumanErrMsg = "%d exploding messages are not available"
DeviceCloneErrMsg = "cloned devices do not support exploding messages"
DeviceCloneWithOneshotErrMsg = "to support exploding messages in `oneshot` mode, you need a separate paper key for each running instance"
DeviceAfterEKErrMsg = "because this device was created after it was sent"
MemberAfterEKErrMsg = "because you joined the team after it was sent"
DeviceStaleErrMsg = "because this device wasn't online to generate an exploding key"
UserStaleErrMsg = "because you weren't online to generate new exploding keys"
)
type IncorrectTeamEphemeralKeyTypeError struct {
expected, actual keybase1.TeamEphemeralKeyType
}
func (e IncorrectTeamEphemeralKeyTypeError) Error() string {
return fmt.Sprintf("Incorrect team ephemeral key type received. Expected: %v, actual %v", e.expected, e.actual)
}
func NewIncorrectTeamEphemeralKeyTypeError(expected, actual keybase1.TeamEphemeralKeyType) IncorrectTeamEphemeralKeyTypeError {
return IncorrectTeamEphemeralKeyTypeError{
expected: expected,
actual: actual,
}
}
func NewNotAuthenticatedForThisDeviceError(mctx libkb.MetaContext, memberCtime *keybase1.Time, contentCtime gregor1.Time) EphemeralKeyError {
var humanMsg string
if deviceProvisionedAfterContentCreation(mctx, &contentCtime) {
humanMsg = DeviceAfterEKErrMsg
} else if memberCtime != nil {
mctx.Debug("NotAuthenticatedForThisDeviceError: memberCtime: %v, contentCtime: %v", memberCtime.Time(), contentCtime.Time())
if contentCtime.Before(gregor1.Time(*memberCtime)) {
humanMsg = MemberAfterEKErrMsg
}
}
return newEphemeralKeyError("message not authenticated for device", humanMsg,
EphemeralKeyErrorKindDEVICENOTAUTHENTICATED, DeviceEKKind)
}
func newEKUnboxErr(mctx libkb.MetaContext, ekKind EphemeralKeyKind, boxGeneration keybase1.EkGeneration,
missingKind EphemeralKeyKind, missingGeneration keybase1.EkGeneration, contentCtime *gregor1.Time) EphemeralKeyError {
debugMsg := fmt.Sprintf("Error unboxing %s@generation:%v missing %s@generation:%v", ekKind, boxGeneration, missingKind, missingGeneration)
var humanMsg string
if deviceProvisionedAfterContentCreation(mctx, contentCtime) {
humanMsg = DeviceAfterEKErrMsg
} else if deviceIsCloned(mctx) {
humanMsg = DeviceCloneErrMsg
if isOneshot, err := mctx.G().IsOneshot(mctx.Ctx()); err != nil {
mctx.Debug("unable to check IsOneshot %v", err)
} else if isOneshot {
humanMsg = DeviceCloneWithOneshotErrMsg
}
}
return newEphemeralKeyError(debugMsg, humanMsg,
EphemeralKeyErrorKindUNBOX, missingKind)
}
func newEKMissingBoxErr(mctx libkb.MetaContext, ekKind EphemeralKeyKind, boxGeneration keybase1.EkGeneration) EphemeralKeyError {
debugMsg := fmt.Sprintf("Missing box for %s@generation:%v", ekKind, boxGeneration)
return newEphemeralKeyError(debugMsg, "", EphemeralKeyErrorKindMISSINGBOX, ekKind)
}
func newTeambotEKWrongKIDErr(mctx libkb.MetaContext, ctime, now keybase1.Time) EphemeralKeyError {
debugMsg := fmt.Sprintf("Wrong KID for %v, first seen at %v, now %v", TeambotEKKind, ctime.Time(), now.Time())
return newEphemeralKeyError(debugMsg, "", EphemeralKeyErrorKindWRONGKID, TeambotEKKind)
}
func newEKCorruptedErr(mctx libkb.MetaContext, ekKind EphemeralKeyKind,
expectedGeneration, boxGeneration keybase1.EkGeneration) EphemeralKeyError {
debugMsg := fmt.Sprintf("Storage error for %s@generation:%v, got generation %v instead", ekKind, boxGeneration, expectedGeneration)
return newEphemeralKeyError(debugMsg, "", EphemeralKeyErrorKindCORRUPTEDGEN, ekKind)
}
func humanMsgWithPrefix(humanMsg string) string {
if humanMsg == "" {
humanMsg = DefaultHumanErrMsg
} else if !strings.Contains(humanMsg, DefaultHumanErrMsg) {
humanMsg = fmt.Sprintf("%s, %s", DefaultHumanErrMsg, humanMsg)
}
return humanMsg
}
func newEphemeralKeyError(debugMsg, humanMsg string, errKind EphemeralKeyErrorKind,
ekKind EphemeralKeyKind) EphemeralKeyError {
humanMsg = humanMsgWithPrefix(humanMsg)
return EphemeralKeyError{
DebugMsg: debugMsg,
HumanMsg: humanMsg,
Ctime: gregor1.ToTime(time.Now()),
ErrKind: errKind,
EKKind: ekKind,
}
}
func newEphemeralKeyErrorFromStatus(e libkb.AppStatusError) EphemeralKeyError {
var errKind EphemeralKeyErrorKind
var ekKind EphemeralKeyKind
var humanMsg string
switch e.Code {
case libkb.SCEphemeralDeviceAfterEK:
errKind = EphemeralKeyErrorKindDEVICEAFTEREK
ekKind = DeviceEKKind
humanMsg = DeviceAfterEKErrMsg
case libkb.SCEphemeralMemberAfterEK:
ekKind = TeamEKKind
humanMsg = MemberAfterEKErrMsg
case libkb.SCEphemeralDeviceStale:
errKind = EphemeralKeyErrorKindDEVICESTALE
ekKind = DeviceEKKind
humanMsg = DeviceStaleErrMsg
case libkb.SCEphemeralUserStale:
errKind = EphemeralKeyErrorKindUSERSTALE
ekKind = UserEKKind
humanMsg = UserStaleErrMsg
}
humanMsg = humanMsgWithPrefix(humanMsg)
return EphemeralKeyError{
DebugMsg: e.Desc,
HumanMsg: humanMsg,
StatusCode: e.Code,
Ctime: gregor1.ToTime(time.Now()),
ErrKind: errKind,
EKKind: ekKind,
}
}
func errFromAppStatus(e error) error {
switch e := e.(type) {
case nil:
return nil
case libkb.AppStatusError:
switch e.Code {
case libkb.SCEphemeralDeviceAfterEK,
libkb.SCEphemeralMemberAfterEK,
libkb.SCEphemeralDeviceStale,
libkb.SCEphemeralUserStale:
return newEphemeralKeyErrorFromStatus(e)
}
}
return e
}
func deviceProvisionedAfterContentCreation(mctx libkb.MetaContext, contentCtime *gregor1.Time) bool {
// some callers may not specify a creation time if they aren't trying to
// decrypt a specific piece of content.
if contentCtime == nil {
return false
}
deviceCtime, err := mctx.ActiveDevice().Ctime(mctx)
if err != nil {
return false
}
return contentCtime.Time().Before(deviceCtime.Time())
}
func deviceIsCloned(mctx libkb.MetaContext) bool {
cloneState, err := libkb.GetDeviceCloneState(mctx)
if err != nil {
return false
}
return cloneState.IsClone()
}
func PluralizeErrorMessage(msg string, count int) string {
if count <= 1 {
return msg
}
msg = strings.Replace(msg, DefaultHumanErrMsg, fmt.Sprintf(DefaultPluralHumanErrMsg, count), 1)
// Backwards compatibility with old server based message which clients may
// have in cache.
msg = strings.Replace(msg, "this message was", "the messages were", 1)
msg = strings.Replace(msg, "the message was", "the messages were", 1)
return msg
}