github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/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  }