diff --git a/pkg/connector/e2ee_keys.go b/pkg/connector/e2ee_keys.go index 9d902d24..04392fe1 100644 --- a/pkg/connector/e2ee_keys.go +++ b/pkg/connector/e2ee_keys.go @@ -73,6 +73,7 @@ func (lc *LineClient) fetchAndUnwrapGroupKey(ctx context.Context, chatMid string Int("group_key_id", sharedKey.GroupKeyID). Int("creator_key_id", sharedKey.CreatorKeyID). Int("receiver_key_id", sharedKey.ReceiverKeyID). + Int("spec_version", sharedKey.SpecVersion). Msg("Fetched group shared key") if _, _, err := lc.ensurePeerKey(ctx, sharedKey.Creator); err != nil { diff --git a/pkg/connector/send_message.go b/pkg/connector/send_message.go index d5031282..050402de 100644 --- a/pkg/connector/send_message.go +++ b/pkg/connector/send_message.go @@ -685,6 +685,7 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat lineMsg.Text = plainTextBody } else { lineMsg.Chunks = chunks + contentMetadata["e2eeVersion"] = strconv.Itoa(e2ee.ChunksVersion(chunks)) } var relatedMsg *database.Message @@ -776,6 +777,7 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat return nil, wrapConvertError(fmt.Errorf("failed to encrypt zipped file message: %w", err)) } lineMsg.Chunks = chunks + contentMetadata["e2eeVersion"] = strconv.Itoa(e2ee.ChunksVersion(chunks)) } retryReqSeq := int(time.Now().UnixMilli() % 1_000_000_000) @@ -801,6 +803,7 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat } if err == nil { lineMsg.Chunks = chunks + contentMetadata["e2eeVersion"] = strconv.Itoa(e2ee.ChunksVersion(chunks)) lineMsg.Text = "" } } diff --git a/pkg/e2ee/manager.go b/pkg/e2ee/manager.go index e146dd7e..634cb295 100644 --- a/pkg/e2ee/manager.go +++ b/pkg/e2ee/manager.go @@ -51,6 +51,8 @@ type Manager struct { groupSessionChannels map[string]map[int]map[int]int latestGroupKey map[string]int // chatMid -> latest groupKeyId + // groupSpecVersion maps chatMid -> specVersion of the latest group key + groupSpecVersion map[string]int groupKeyLRU []groupKeyRef groupNativeObjects int @@ -83,6 +85,7 @@ func NewManagerWithRunner(r *gen.Runner) *Manager { groupKeys: make(map[string]map[int]int), groupSessionChannels: make(map[string]map[int]map[int]int), latestGroupKey: make(map[string]int), + groupSpecVersion: make(map[string]int), } } @@ -104,6 +107,7 @@ func (m *Manager) Close() { m.groupKeys = nil m.groupSessionChannels = nil m.latestGroupKey = nil + m.groupSpecVersion = nil m.groupKeyLRU = nil m.groupNativeObjects = 0 } @@ -540,6 +544,18 @@ func assembleCipherV1(chunks []string) ([]byte, error) { return append(append(b0, b1...), b2...), nil } +// ChunksVersion returns the Letter Sealing version (1 or 2) of encrypted message chunks. +func ChunksVersion(chunks []string) int { + if len(chunks) == 0 { + return 2 + } + ver, err := cipherVersion(chunks) + if err != nil { + return 2 + } + return ver +} + func cipherVersion(chunks []string) (int, error) { first, err := base64.StdEncoding.DecodeString(strings.TrimSpace(chunks[0])) if err != nil { @@ -561,6 +577,7 @@ func (m *Manager) UnwrapGroupSharedKey(chatMid string, sharedKey *line.E2EEGroup ref := groupKeyRef{chatMid, sharedKey.GroupKeyID} if id := m.groupKeys[chatMid][sharedKey.GroupKeyID]; id != 0 { m.latestGroupKey[chatMid] = sharedKey.GroupKeyID + m.groupSpecVersion[chatMid] = sharedKey.SpecVersion m.touchGroupKeyLocked(ref) return id, nil } @@ -599,6 +616,7 @@ func (m *Manager) UnwrapGroupSharedKey(chatMid string, sharedKey *line.E2EEGroup m.groupKeys[chatMid][sharedKey.GroupKeyID] = unwrappedKeyID m.latestGroupKey[chatMid] = sharedKey.GroupKeyID + m.groupSpecVersion[chatMid] = sharedKey.SpecVersion m.groupNativeObjects++ m.touchGroupKeyLocked(ref) @@ -631,6 +649,7 @@ func (m *Manager) evictGroupKeysLocked() error { } if m.latestGroupKey[ref.chatMid] == ref.groupKeyID { delete(m.latestGroupKey, ref.chatMid) + delete(m.groupSpecVersion, ref.chatMid) } if err := m.runner.KeyDestroy(keyID); err != nil && isFatalLTSMError(err) { return err @@ -860,6 +879,10 @@ func (m *Manager) EncryptGroupMessageRaw(chatMid, fromMid string, contentType in return nil, err } + if m.groupSpecVersion[chatMid] == 1 { + return m.encryptV1Chunks(chanID, myKeyID, groupKeyID, payload) + } + ctB64, err := m.runner.ChannelEncryptV2(chanID, chatMid, fromMid, myKeyID, groupKeyID, contentType, seq, string(payload)) if err != nil { if isFatalLTSMError(err) {