github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/storage/checkers.go (about) 1 package storage 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/hex" 7 "fmt" 8 9 "github.com/keybase/client/go/chat/globals" 10 "github.com/keybase/client/go/libkb" 11 "github.com/keybase/client/go/protocol/chat1" 12 ) 13 14 type BodyHashChecker func(bodyHash chat1.Hash, uniqueMsgID chat1.MessageID, uniqueConvID chat1.ConversationID) error 15 type PrevChecker func(msgID chat1.MessageID, convID chat1.ConversationID, uniqueHeaderHash chat1.Hash) error 16 17 // These are globally unique. They don't include the UID. 18 func makeBodyHashIndexKey(bodyHash chat1.Hash) libkb.DbKey { 19 return libkb.DbKey{ 20 Typ: libkb.DBChatBodyHashIndex, 21 Key: fmt.Sprintf("bodyhash:%s", bodyHash), 22 } 23 } 24 25 // CheckAndRecordBodyHash checks the current message's body hash against all the body hashes we've 26 // seen, to prevent replays. If the header hash is new, add it to the set. 27 func CheckAndRecordBodyHash(ctx context.Context, g *globals.Context, bodyHash chat1.Hash, uniqueMsgID chat1.MessageID, uniqueConvID chat1.ConversationID) error { 28 bodyHashKey := makeBodyHashIndexKey(bodyHash) 29 bodyHashValue := []byte(fmt.Sprintf("%s:%s", uniqueConvID, uniqueMsgID)) 30 existingVal, found, err := g.LocalChatDb.GetRaw(bodyHashKey) 31 // Log errors as warnings, and skip this check. That prevents a corrupt 32 // leveldb cache from breaking chat. 33 if err != nil { 34 g.Log.CDebugf(ctx, "error getting body hash key from chat db: %s", err) 35 return nil 36 } 37 if found { 38 if !bytes.Equal(existingVal, bodyHashValue) { 39 err := fmt.Errorf("chat message body hash replay detected, %s != %s", string(existingVal), string(bodyHashValue)) 40 g.Log.CDebugf(ctx, "%s", err) 41 return err 42 } 43 return nil 44 } 45 err = g.LocalChatDb.PutRaw(bodyHashKey, bodyHashValue) 46 // Also suppress write errors. 47 if err != nil { 48 g.Log.CDebugf(ctx, "error writing body hash key to chat db: %s", err) 49 } 50 return nil 51 } 52 53 // These are globally unique. They don't include the UID. 54 func makePrevIndexKey(convID chat1.ConversationID, msgID chat1.MessageID) libkb.DbKey { 55 return libkb.DbKey{ 56 Typ: libkb.DBChatBodyHashIndex, 57 Key: fmt.Sprintf("prev:%s:%s", hex.EncodeToString(convID), msgID), 58 } 59 } 60 61 func makePrevIndexValue(headerHash chat1.Hash) []byte { 62 return []byte(hex.EncodeToString(headerHash)) 63 } 64 65 // CheckAndRecordPrevPointer checks the current message's header hash against all the prev pointers we've 66 // ever seen. If the current message is new, add it to the set. 67 func CheckAndRecordPrevPointer(ctx context.Context, g *globals.Context, msgID chat1.MessageID, convID chat1.ConversationID, uniqueHeaderHash chat1.Hash) error { 68 prevKey := makePrevIndexKey(convID, msgID) 69 headerHashVal := makePrevIndexValue(uniqueHeaderHash) 70 existingVal, found, err := g.LocalChatDb.GetRaw(prevKey) 71 // Log errors as warnings, and skip this check. That prevents a corrupt 72 // leveldb cache from breaking chat. 73 if err != nil { 74 g.Log.CWarningf(ctx, "error getting prev pointer key from chat db: %s", err) 75 return nil 76 } 77 if found { 78 if !bytes.Equal(existingVal, headerHashVal) { 79 g.Log.CDebugf(ctx, "chat message prev pointer inconsistency detected") 80 g.Log.CDebugf(ctx, "in conv_id %s, msg_id %s", convID.String(), msgID.String()) 81 g.Log.CDebugf(ctx, "mismatch: %s (stored) != %s (new)", string(existingVal), string(headerHashVal)) 82 err := fmt.Errorf("chat message prev pointer inconsistency detected, %s != %s", string(existingVal), string(headerHashVal)) 83 return err 84 } 85 return nil 86 } 87 g.Log.CDebugf(ctx, "storing header hash %s for conv_id %s, msg_id %s", string(headerHashVal), convID.String(), msgID.String()) 88 err = g.LocalChatDb.PutRaw(prevKey, headerHashVal) 89 // Also suppress write errors. 90 if err != nil { 91 g.Log.CDebugf(ctx, "error writing body hash key to chat db: %s", err) 92 } 93 return nil 94 }