Files
im_server/server/store/message_crypto_test.go
T
Austin 8228ceb89f fix: 处理撤回消息解密时的500错误
撤回消息会清空content但保留key_version,新增判断跳过此类消息解密,避免整个查询窗口被毒化
2026-10-08 19:43:27 +08:00

115 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package store
import (
"bytes"
"testing"
"time"
"github.com/tinode/chat/server/store/types"
)
func TestMessageCryptoRoundTrip(t *testing.T) {
tenantID := types.TenantID(9001)
key := bytes.Repeat([]byte{7}, 32)
messageKeyCache.Lock()
expires := time.Now().Add(time.Minute)
messageKeyCache.items[messageKeyCacheID(tenantID, 0)] = cachedMessageKey{enabled: true, version: 3, key: key, expires: expires}
messageKeyCache.items[messageKeyCacheID(tenantID, 3)] = cachedMessageKey{enabled: true, version: 3, key: key, expires: expires}
messageKeyCache.Unlock()
defer InvalidateMessageEncryptionKeys(tenantID)
original := &types.Message{TenantID: tenantID, Topic: "p2p-test", SeqId: 42, Content: map[string]any{"txt": "hello"}}
stored, err := encryptMessageContent(original)
if err != nil {
t.Fatal(err)
}
if stored.KeyVersion != 3 || len(stored.ContentNonce) != 12 || stored.Content == original.Content {
t.Fatalf("message was not encrypted: %#v", stored)
}
if err = decryptMessageContent(stored); err != nil {
t.Fatal(err)
}
content, ok := stored.Content.(map[string]any)
if !ok || content["txt"] != "hello" {
t.Fatalf("unexpected plaintext: %#v", stored.Content)
}
}
func TestMessageCryptoPlaintextWhenDisabled(t *testing.T) {
tenantID := types.TenantID(9002)
messageKeyCache.Lock()
messageKeyCache.items[messageKeyCacheID(tenantID, 0)] = cachedMessageKey{expires: time.Now().Add(time.Minute)}
messageKeyCache.Unlock()
defer InvalidateMessageEncryptionKeys(tenantID)
original := &types.Message{TenantID: tenantID, Topic: "grp-test", SeqId: 7, Content: "plain"}
stored, err := encryptMessageContent(original)
if err != nil {
t.Fatal(err)
}
if stored.KeyVersion != 0 || stored.Content != "plain" || len(stored.ContentNonce) != 0 {
t.Fatalf("unexpected encrypted message: %#v", stored)
}
}
func TestMessageCryptoRejectsTampering(t *testing.T) {
tenantID := types.TenantID(9003)
key := bytes.Repeat([]byte{9}, 32)
expires := time.Now().Add(time.Minute)
messageKeyCache.Lock()
messageKeyCache.items[messageKeyCacheID(tenantID, 0)] = cachedMessageKey{enabled: true, version: 4, key: key, expires: expires}
messageKeyCache.items[messageKeyCacheID(tenantID, 4)] = cachedMessageKey{enabled: true, version: 4, key: key, expires: expires}
messageKeyCache.Unlock()
defer InvalidateMessageEncryptionKeys(tenantID)
original := &types.Message{TenantID: tenantID, Topic: "p2p-secure", SeqId: 8, Content: map[string]any{"txt": "secret"}}
stored, err := encryptMessageContent(original)
if err != nil {
t.Fatal(err)
}
stored.Topic = "p2p-tampered"
if err = decryptMessageContent(stored); err == nil {
t.Fatal("expected authenticated decryption to reject changed topic")
}
stored, err = encryptMessageContent(original)
if err != nil {
t.Fatal(err)
}
stored.ContentNonce = stored.ContentNonce[:len(stored.ContentNonce)-1]
if err = decryptMessageContent(stored); err == nil {
t.Fatal("expected invalid nonce length to be rejected")
}
}
// 撤回消息 content 已被清 NULL,但 recall UPDATE 保留了 content_nonce/key_version;
// 读路径解密必须跳过此类行,否则单条撤回消息会把整个 get data 窗口毒化成 500(2026-10-08 测试环境实案)。
func TestMessageCryptoSkipsRecalledContent(t *testing.T) {
tenantID := types.TenantID(9004)
key := bytes.Repeat([]byte{5}, 32)
expires := time.Now().Add(time.Minute)
messageKeyCache.Lock()
messageKeyCache.items[messageKeyCacheID(tenantID, 0)] = cachedMessageKey{enabled: true, version: 2, key: key, expires: expires}
messageKeyCache.items[messageKeyCacheID(tenantID, 2)] = cachedMessageKey{enabled: true, version: 2, key: key, expires: expires}
messageKeyCache.Unlock()
defer InvalidateMessageEncryptionKeys(tenantID)
recalled := &types.Message{TenantID: tenantID, Topic: "p2p-recalled", SeqId: 64, KeyVersion: 2, ContentNonce: bytes.Repeat([]byte{1}, 12), Content: nil}
if err := decryptMessageContent(recalled); err != nil {
t.Fatalf("expected recalled message with nil content to be skipped, got: %v", err)
}
if recalled.Content != nil {
t.Fatalf("nil content should stay nil: %#v", recalled.Content)
}
}
func TestMessageCryptoSearchMatchesDecryptedContent(t *testing.T) {
content := map[string]any{"txt": "项目 验收通过", "nested": map[string]any{"title": "日报"}}
if !messageContentMatchesSearch(content, "验收") {
t.Fatal("expected plaintext keyword to match")
}
if messageContentMatchesSearch(content, "不存在") {
t.Fatal("unexpected keyword match")
}
}