github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/ephemeral/errors.go (about) 1 package ephemeral 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/keybase/client/go/libkb" 9 "github.com/keybase/client/go/protocol/gregor1" 10 "github.com/keybase/client/go/protocol/keybase1" 11 ) 12 13 type EphemeralKeyKind string 14 15 const ( 16 DeviceEKKind EphemeralKeyKind = "deviceEK" 17 UserEKKind EphemeralKeyKind = "userEK" 18 TeamEKKind EphemeralKeyKind = "teamEK" 19 TeambotEKKind EphemeralKeyKind = "teambotEK" 20 ) 21 22 type EphemeralKeyErrorKind int 23 24 const ( 25 EphemeralKeyErrorKindDEVICENOTAUTHENTICATED EphemeralKeyErrorKind = iota 26 EphemeralKeyErrorKindUNBOX 27 EphemeralKeyErrorKindMISSINGBOX 28 EphemeralKeyErrorKindWRONGKID 29 EphemeralKeyErrorKindCORRUPTEDGEN 30 EphemeralKeyErrorKindDEVICEAFTEREK 31 EphemeralKeyErrorKindMEMBERAFTEREK 32 EphemeralKeyErrorKindDEVICESTALE 33 EphemeralKeyErrorKindUSERSTALE 34 EphemeralKeyErrorKindUNKNOWN 35 ) 36 37 type EphemeralKeyError struct { 38 DebugMsg string 39 HumanMsg string 40 StatusCode int 41 Ctime gregor1.Time 42 ErrKind EphemeralKeyErrorKind 43 EKKind EphemeralKeyKind 44 IsTransient bool 45 } 46 47 func (e EphemeralKeyError) HumanError() string { 48 return e.HumanMsg 49 } 50 51 func (e EphemeralKeyError) Error() string { 52 return e.DebugMsg 53 } 54 55 // AllowTransient determines if we allow the given error to be downgraded to a 56 // transient error. If we encounter a MISSINGBOX error for a TeambotEK we 57 // allow this to be marked as transient for a 24 hour window. The intention is 58 // to allow a chat message to be retried on send for this period instead of 59 // permanently failing. 60 func (e EphemeralKeyError) AllowTransient() bool { 61 return (e.EKKind == TeambotEKKind && 62 e.ErrKind == EphemeralKeyErrorKindMISSINGBOX && 63 time.Since(e.Ctime.Time()) < time.Hour*24) 64 } 65 66 func (e EphemeralKeyError) IsPermanent() bool { 67 return !e.IsTransient 68 } 69 70 func newTransientEphemeralKeyError(err EphemeralKeyError) EphemeralKeyError { 71 return EphemeralKeyError{ 72 DebugMsg: err.DebugMsg, 73 HumanMsg: err.HumanMsg, 74 StatusCode: err.StatusCode, 75 Ctime: err.Ctime, 76 ErrKind: err.ErrKind, 77 EKKind: err.EKKind, 78 IsTransient: true, 79 } 80 } 81 82 const ( 83 DefaultHumanErrMsg = "This exploding message is not available" 84 DefaultPluralHumanErrMsg = "%d exploding messages are not available" 85 DeviceCloneErrMsg = "cloned devices do not support exploding messages" 86 DeviceCloneWithOneshotErrMsg = "to support exploding messages in `oneshot` mode, you need a separate paper key for each running instance" 87 DeviceAfterEKErrMsg = "because this device was created after it was sent" 88 MemberAfterEKErrMsg = "because you joined the team after it was sent" 89 DeviceStaleErrMsg = "because this device wasn't online to generate an exploding key" 90 UserStaleErrMsg = "because you weren't online to generate new exploding keys" 91 ) 92 93 type IncorrectTeamEphemeralKeyTypeError struct { 94 expected, actual keybase1.TeamEphemeralKeyType 95 } 96 97 func (e IncorrectTeamEphemeralKeyTypeError) Error() string { 98 return fmt.Sprintf("Incorrect team ephemeral key type received. Expected: %v, actual %v", e.expected, e.actual) 99 } 100 101 func NewIncorrectTeamEphemeralKeyTypeError(expected, actual keybase1.TeamEphemeralKeyType) IncorrectTeamEphemeralKeyTypeError { 102 return IncorrectTeamEphemeralKeyTypeError{ 103 expected: expected, 104 actual: actual, 105 } 106 } 107 108 func NewNotAuthenticatedForThisDeviceError(mctx libkb.MetaContext, memberCtime *keybase1.Time, contentCtime gregor1.Time) EphemeralKeyError { 109 var humanMsg string 110 if deviceProvisionedAfterContentCreation(mctx, &contentCtime) { 111 humanMsg = DeviceAfterEKErrMsg 112 } else if memberCtime != nil { 113 mctx.Debug("NotAuthenticatedForThisDeviceError: memberCtime: %v, contentCtime: %v", memberCtime.Time(), contentCtime.Time()) 114 if contentCtime.Before(gregor1.Time(*memberCtime)) { 115 humanMsg = MemberAfterEKErrMsg 116 } 117 } 118 return newEphemeralKeyError("message not authenticated for device", humanMsg, 119 EphemeralKeyErrorKindDEVICENOTAUTHENTICATED, DeviceEKKind) 120 } 121 122 func newEKUnboxErr(mctx libkb.MetaContext, ekKind EphemeralKeyKind, boxGeneration keybase1.EkGeneration, 123 missingKind EphemeralKeyKind, missingGeneration keybase1.EkGeneration, contentCtime *gregor1.Time) EphemeralKeyError { 124 debugMsg := fmt.Sprintf("Error unboxing %s@generation:%v missing %s@generation:%v", ekKind, boxGeneration, missingKind, missingGeneration) 125 var humanMsg string 126 if deviceProvisionedAfterContentCreation(mctx, contentCtime) { 127 humanMsg = DeviceAfterEKErrMsg 128 } else if deviceIsCloned(mctx) { 129 humanMsg = DeviceCloneErrMsg 130 if isOneshot, err := mctx.G().IsOneshot(mctx.Ctx()); err != nil { 131 mctx.Debug("unable to check IsOneshot %v", err) 132 } else if isOneshot { 133 humanMsg = DeviceCloneWithOneshotErrMsg 134 } 135 } 136 return newEphemeralKeyError(debugMsg, humanMsg, 137 EphemeralKeyErrorKindUNBOX, missingKind) 138 } 139 140 func newEKMissingBoxErr(mctx libkb.MetaContext, ekKind EphemeralKeyKind, boxGeneration keybase1.EkGeneration) EphemeralKeyError { 141 debugMsg := fmt.Sprintf("Missing box for %s@generation:%v", ekKind, boxGeneration) 142 return newEphemeralKeyError(debugMsg, "", EphemeralKeyErrorKindMISSINGBOX, ekKind) 143 } 144 145 func newTeambotEKWrongKIDErr(mctx libkb.MetaContext, ctime, now keybase1.Time) EphemeralKeyError { 146 debugMsg := fmt.Sprintf("Wrong KID for %v, first seen at %v, now %v", TeambotEKKind, ctime.Time(), now.Time()) 147 return newEphemeralKeyError(debugMsg, "", EphemeralKeyErrorKindWRONGKID, TeambotEKKind) 148 } 149 150 func newEKCorruptedErr(mctx libkb.MetaContext, ekKind EphemeralKeyKind, 151 expectedGeneration, boxGeneration keybase1.EkGeneration) EphemeralKeyError { 152 debugMsg := fmt.Sprintf("Storage error for %s@generation:%v, got generation %v instead", ekKind, boxGeneration, expectedGeneration) 153 return newEphemeralKeyError(debugMsg, "", EphemeralKeyErrorKindCORRUPTEDGEN, ekKind) 154 } 155 156 func humanMsgWithPrefix(humanMsg string) string { 157 if humanMsg == "" { 158 humanMsg = DefaultHumanErrMsg 159 } else if !strings.Contains(humanMsg, DefaultHumanErrMsg) { 160 humanMsg = fmt.Sprintf("%s, %s", DefaultHumanErrMsg, humanMsg) 161 } 162 return humanMsg 163 } 164 165 func newEphemeralKeyError(debugMsg, humanMsg string, errKind EphemeralKeyErrorKind, 166 ekKind EphemeralKeyKind) EphemeralKeyError { 167 humanMsg = humanMsgWithPrefix(humanMsg) 168 return EphemeralKeyError{ 169 DebugMsg: debugMsg, 170 HumanMsg: humanMsg, 171 Ctime: gregor1.ToTime(time.Now()), 172 ErrKind: errKind, 173 EKKind: ekKind, 174 } 175 } 176 func newEphemeralKeyErrorFromStatus(e libkb.AppStatusError) EphemeralKeyError { 177 var errKind EphemeralKeyErrorKind 178 var ekKind EphemeralKeyKind 179 var humanMsg string 180 switch e.Code { 181 case libkb.SCEphemeralDeviceAfterEK: 182 errKind = EphemeralKeyErrorKindDEVICEAFTEREK 183 ekKind = DeviceEKKind 184 humanMsg = DeviceAfterEKErrMsg 185 case libkb.SCEphemeralMemberAfterEK: 186 ekKind = TeamEKKind 187 humanMsg = MemberAfterEKErrMsg 188 case libkb.SCEphemeralDeviceStale: 189 errKind = EphemeralKeyErrorKindDEVICESTALE 190 ekKind = DeviceEKKind 191 humanMsg = DeviceStaleErrMsg 192 case libkb.SCEphemeralUserStale: 193 errKind = EphemeralKeyErrorKindUSERSTALE 194 ekKind = UserEKKind 195 humanMsg = UserStaleErrMsg 196 } 197 198 humanMsg = humanMsgWithPrefix(humanMsg) 199 return EphemeralKeyError{ 200 DebugMsg: e.Desc, 201 HumanMsg: humanMsg, 202 StatusCode: e.Code, 203 Ctime: gregor1.ToTime(time.Now()), 204 ErrKind: errKind, 205 EKKind: ekKind, 206 } 207 } 208 209 func errFromAppStatus(e error) error { 210 switch e := e.(type) { 211 case nil: 212 return nil 213 case libkb.AppStatusError: 214 switch e.Code { 215 case libkb.SCEphemeralDeviceAfterEK, 216 libkb.SCEphemeralMemberAfterEK, 217 libkb.SCEphemeralDeviceStale, 218 libkb.SCEphemeralUserStale: 219 return newEphemeralKeyErrorFromStatus(e) 220 } 221 } 222 return e 223 } 224 225 func deviceProvisionedAfterContentCreation(mctx libkb.MetaContext, contentCtime *gregor1.Time) bool { 226 // some callers may not specify a creation time if they aren't trying to 227 // decrypt a specific piece of content. 228 if contentCtime == nil { 229 return false 230 } 231 deviceCtime, err := mctx.ActiveDevice().Ctime(mctx) 232 if err != nil { 233 return false 234 } 235 return contentCtime.Time().Before(deviceCtime.Time()) 236 } 237 238 func deviceIsCloned(mctx libkb.MetaContext) bool { 239 cloneState, err := libkb.GetDeviceCloneState(mctx) 240 if err != nil { 241 return false 242 } 243 return cloneState.IsClone() 244 } 245 246 func PluralizeErrorMessage(msg string, count int) string { 247 if count <= 1 { 248 return msg 249 } 250 msg = strings.Replace(msg, DefaultHumanErrMsg, fmt.Sprintf(DefaultPluralHumanErrMsg, count), 1) 251 // Backwards compatibility with old server based message which clients may 252 // have in cache. 253 msg = strings.Replace(msg, "this message was", "the messages were", 1) 254 msg = strings.Replace(msg, "the message was", "the messages were", 1) 255 return msg 256 }