From f5c19c1b6e054c5e0d417657d0307569406c11f5 Mon Sep 17 00:00:00 2001 From: Mateusz Dahlke <39696234+Xavrax@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:56:35 +0100 Subject: [PATCH] Handle unencrypted message while getting messages with crypto (#158) --- .pubnub.yml | 9 ++- CHANGELOG.md | 6 ++ crypto/legacy_cryptor.go | 5 ++ fetch_request.go | 5 +- history_request.go | 5 +- history_request_test.go | 45 ++++++++++++++ listener_manager.go | 2 + pubnub.go | 2 +- subscription_manager.go | 19 +++--- subscription_manager_test.go | 116 +++++++++++++++++++++++++++++++++++ 10 files changed, 200 insertions(+), 14 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 4d9ddc67..1159a0c2 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,6 +1,11 @@ --- -version: v7.2.0 +version: v7.2.1 changelog: + - date: 2023-11-27 + version: v7.2.1 + changes: + - type: bug + text: "Handle unencrypted message while getting messages with crypto." - date: 2023-10-16 version: v7.2.0 changes: @@ -740,7 +745,7 @@ sdks: distribution-type: package distribution-repository: GitHub package-name: Go - location: https://github.com/pubnub/go/releases/tag/v7.2.0 + location: https://github.com/pubnub/go/releases/tag/v7.2.1 requires: - name: "Go" diff --git a/CHANGELOG.md b/CHANGELOG.md index 074c9bd3..23ef2b7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v7.2.1 +November 27 2023 + +#### Fixed +- Handle unencrypted message while getting messages with crypto. + ## v7.2.0 October 16 2023 diff --git a/crypto/legacy_cryptor.go b/crypto/legacy_cryptor.go index a7226fb9..9f1d393a 100644 --- a/crypto/legacy_cryptor.go +++ b/crypto/legacy_cryptor.go @@ -60,7 +60,12 @@ func (c *legacyCryptor) Encrypt(message []byte) (*EncryptedData, error) { func (c *legacyCryptor) Decrypt(encryptedData *EncryptedData) (r []byte, e error) { iv := make([]byte, aes.BlockSize) data := encryptedData.Data + if c.randomIv { + if (len(data) < aes.BlockSize) { + return nil, fmt.Errorf("length of data to decrypt should be at least %d", aes.BlockSize) + } + iv = data[:aes.BlockSize] data = data[aes.BlockSize:] } else { diff --git a/fetch_request.go b/fetch_request.go index 879c547b..de4253cd 100644 --- a/fetch_request.go +++ b/fetch_request.go @@ -290,12 +290,13 @@ func (o *fetchOpts) fetchMessages(channels map[string]interface{}) map[string][] for _, val := range histResponseMap { if histResponse, ok3 := val.(map[string]interface{}); ok3 { - msg, _ := parseCipherInterface(histResponse["message"], o.pubnub.Config, o.pubnub.getCryptoModule()) + msg, err := parseCipherInterface(histResponse["message"], o.pubnub.Config, o.pubnub.getCryptoModule()) histItem := FetchResponseItem{ Message: msg, Timetoken: histResponse["timetoken"].(string), Meta: histResponse["meta"], + Error: err, } if d, ok := histResponse["message_type"]; ok { switch v := d.(type) { @@ -385,6 +386,7 @@ type FetchResponse struct { } // FetchResponseItem contains the message and the associated timetoken. +// It can contain the error if the message is not decrypted properly assuming the message is not encrypted. type FetchResponseItem struct { Message interface{} `json:"message"` Meta interface{} `json:"meta"` @@ -393,6 +395,7 @@ type FetchResponseItem struct { Timetoken string `json:"timetoken"` UUID string `json:"uuid"` MessageType int `json:"message_type"` + Error error } // PNHistoryMessageActionsTypeMap is the struct used in the Fetch request that includes Message Actions diff --git a/history_request.go b/history_request.go index 5c51e51d..46111c17 100644 --- a/history_request.go +++ b/history_request.go @@ -202,6 +202,7 @@ type HistoryResponseItem struct { Message interface{} Meta interface{} Timetoken int64 + Error error } func logAndCreateNewResponseParsingError(o *historyOpts, err error, jsonBody string, message string) *pnerr.ResponseParsingError { @@ -224,7 +225,7 @@ func getHistoryItemsWithoutTimetoken(historyResponseRaw []byte, o *historyOpts, for i, v := range historyResponseItems { o.pubnub.Config.Log.Println(v) - items[i].Message, _ = parseCipherInterface(v, o.pubnub.Config, o.pubnub.getCryptoModule()) + items[i].Message, items[i].Error = parseCipherInterface(v, o.pubnub.Config, o.pubnub.getCryptoModule()) } return items, nil } @@ -237,7 +238,7 @@ func getHistoryItemsWithTimetoken(historyResponseItems []HistoryResponseItem, o for i, v := range historyResponseItems { if v.Message != nil { o.pubnub.Config.Log.Println(v.Message) - items[i].Message, _ = parseCipherInterface(v.Message, o.pubnub.Config, o.pubnub.getCryptoModule()) + items[i].Message, items[i].Error = parseCipherInterface(v.Message, o.pubnub.Config, o.pubnub.getCryptoModule()) o.pubnub.Config.Log.Println(v.Timetoken) items[i].Timetoken = v.Timetoken diff --git a/history_request_test.go b/history_request_test.go index 8eb31d6b..927fbe21 100644 --- a/history_request_test.go +++ b/history_request_test.go @@ -7,6 +7,7 @@ import ( "strconv" "testing" + "github.com/pubnub/go/v7/crypto" h "github.com/pubnub/go/v7/tests/helpers" "github.com/stretchr/testify/assert" ) @@ -425,6 +426,7 @@ func TestHistoryEncrypt(t *testing.T) { messages := resp.Messages assert.Equal("hey", messages[0].Message) + assert.Nil(messages[0].Error) pnconfig.CipherKey = "" } @@ -550,5 +552,48 @@ func TestHistoryResponseEndTTError(t *testing.T) { assert.Equal(int64(121324), resp.StartTimetoken) assert.Equal(int64(0), resp.EndTimetoken) assert.Nil(err) +} + +func TestHistoryCryptoModuleWithEncryptedMessage(t *testing.T) { + assert := assert.New(t) + pnconfig := NewDemoConfig() + pubnub := NewPubNub(pnconfig) + crypto, init_err := crypto.NewAesCbcCryptoModule("enigma", true) + + assert.Nil(init_err) + + pubnub.Config.CryptoModule = crypto + + // Rust generated cipher text + jsonString := []byte(`[["UE5FRAFBQ1JIEALf+E65kseYJwTw2J6BUk9MePHiCcBCS+8ykXLkBIOA"],14991775432719844,14991868111600528]`) + + resp, _, err := newHistoryResponse(jsonString, pubnub.initHistoryOpts(), fakeResponseState) + assert.Nil(err) + + messages := resp.Messages + assert.Equal("test", messages[0].Message) + assert.Nil(messages[0].Error) + pnconfig.CipherKey = "" +} + +func TestHistoryCryptoModuleWithNoEncryptedMessage(t *testing.T) { + assert := assert.New(t) + pnconfig := NewDemoConfig() + pubnub := NewPubNub(pnconfig) + crypto, init_err := crypto.NewAesCbcCryptoModule("enigma", true) + + assert.Nil(init_err) + + pubnub.Config.CryptoModule = crypto + jsonString := []byte(`[["test"],14991775432719844,14991868111600528]`) + + resp, _, err := newHistoryResponse(jsonString, pubnub.initHistoryOpts(), fakeResponseState) + assert.Nil(err) + + messages := resp.Messages + assert.Equal("test", messages[0].Message) + assert.NotNil(messages[0].Error) + pnconfig.CipherKey = "" } + diff --git a/listener_manager.go b/listener_manager.go index 9cbc02f7..c4f279ab 100644 --- a/listener_manager.go +++ b/listener_manager.go @@ -270,6 +270,7 @@ type PNMessage struct { Subscription string Publisher string Timetoken int64 + Error error } // PNPresence is the Message Response for Presence @@ -360,4 +361,5 @@ type PNFilesEvent struct { Subscription string Publisher string Timetoken int64 + Error error } diff --git a/pubnub.go b/pubnub.go index e3e3f8ab..93c2a585 100644 --- a/pubnub.go +++ b/pubnub.go @@ -15,7 +15,7 @@ import ( // Default constants const ( // Version :the version of the SDK - Version = "7.2.0" + Version = "7.2.1" // MaxSequence for publish messages MaxSequence = 65535 ) diff --git a/subscription_manager.go b/subscription_manager.go index 1873f313..cbbe4913 100644 --- a/subscription_manager.go +++ b/subscription_manager.go @@ -638,7 +638,7 @@ func processNonPresencePayload(m *SubscriptionManager, payload subscribeMessage, switch payload.MessageType { case PNMessageTypeSignal: - pnMessageResult := createPNMessageResult(payload.Payload, actualCh, subscribedCh, channel, subscriptionMatch, payload.IssuingClientID, payload.UserMetadata, timetoken) + pnMessageResult := createPNMessageResult(payload.Payload, actualCh, subscribedCh, channel, subscriptionMatch, payload.IssuingClientID, payload.UserMetadata, timetoken, /*no error*/nil) m.pubnub.Config.Log.Println("announceSignal,", pnMessageResult) m.listenerManager.announceSignal(pnMessageResult) case PNMessageTypeObjects: @@ -675,7 +675,7 @@ func processNonPresencePayload(m *SubscriptionManager, payload subscribeMessage, } - pnFilesEvent := createPNFilesEvent(messagePayload, m, actualCh, subscribedCh, channel, subscriptionMatch, payload.IssuingClientID, payload.UserMetadata, timetoken) + pnFilesEvent := createPNFilesEvent(messagePayload, m, actualCh, subscribedCh, channel, subscriptionMatch, payload.IssuingClientID, payload.UserMetadata, timetoken, err) m.pubnub.Config.Log.Println("PNMessageTypeFile:", PNMessageTypeFile) m.listenerManager.announceFile(pnFilesEvent) default: @@ -693,7 +693,7 @@ func processNonPresencePayload(m *SubscriptionManager, payload subscribeMessage, m.listenerManager.announceStatus(pnStatus) } - pnMessageResult := createPNMessageResult(messagePayload, actualCh, subscribedCh, channel, subscriptionMatch, payload.IssuingClientID, payload.UserMetadata, timetoken) + pnMessageResult := createPNMessageResult(messagePayload, actualCh, subscribedCh, channel, subscriptionMatch, payload.IssuingClientID, payload.UserMetadata, timetoken, err) m.pubnub.Config.Log.Println("announceMessage,", pnMessageResult) m.listenerManager.announceMessage(pnMessageResult) } @@ -716,7 +716,7 @@ func processSubscribePayload(m *SubscriptionManager, payload subscribeMessage) { } } -func createPNFilesEvent(filePayload interface{}, m *SubscriptionManager, actualCh, subscribedCh, channel, subscriptionMatch, issuingClientID string, userMetadata interface{}, timetoken int64) *PNFilesEvent { +func createPNFilesEvent(filePayload interface{}, m *SubscriptionManager, actualCh, subscribedCh, channel, subscriptionMatch, issuingClientID string, userMetadata interface{}, timetoken int64, err error) *PNFilesEvent { var filesPayload map[string]interface{} var ok bool if filesPayload, ok = filePayload.(map[string]interface{}); !ok { @@ -747,6 +747,7 @@ func createPNFilesEvent(filePayload interface{}, m *SubscriptionManager, actualC Timetoken: timetoken, Publisher: issuingClientID, UserMetadata: userMetadata, + Error: err, } return pnFilesEvent } @@ -933,7 +934,7 @@ func createPNObjectsResult(objPayload interface{}, m *SubscriptionManager, actua return pnUUIDEvent, pnChannelEvent, pnMembershipEvent, eventType } -func createPNMessageResult(messagePayload interface{}, actualCh, subscribedCh, channel, subscriptionMatch, issuingClientID string, userMetadata interface{}, timetoken int64) *PNMessage { +func createPNMessageResult(messagePayload interface{}, actualCh, subscribedCh, channel, subscriptionMatch, issuingClientID string, userMetadata interface{}, timetoken int64, error error) *PNMessage { pnMessageResult := &PNMessage{ Message: messagePayload, @@ -944,10 +945,10 @@ func createPNMessageResult(messagePayload interface{}, actualCh, subscribedCh, c Timetoken: timetoken, Publisher: issuingClientID, UserMetadata: userMetadata, + Error: error, } return pnMessageResult - } // parseCipherInterface handles the decryption in case a cipher key is used @@ -971,7 +972,8 @@ func parseCipherInterface(data interface{}, pnConf *Config, module crypto.Crypto pnConf.Log.Println("v[pn_other]", v["pn_other"], v, msg) decrypted, errDecryption := decryptString(module, msg) if errDecryption != nil { - pnConf.Log.Println(errDecryption, msg) + pnConf.Log.Println(errDecryption, msg, "\nMessage might be not encrypted, returning as is...") + return v, errDecryption } else { var intf interface{} @@ -994,7 +996,8 @@ func parseCipherInterface(data interface{}, pnConf *Config, module crypto.Crypto var intf interface{} decrypted, errDecryption := decryptString(module, data.(string)) if errDecryption != nil { - pnConf.Log.Println(errDecryption, intf) + pnConf.Log.Println(errDecryption, intf, "\nMessage might be not encrypted, returning as is...") + intf = data return intf, errDecryption } diff --git a/subscription_manager_test.go b/subscription_manager_test.go index af956a08..47f8243f 100644 --- a/subscription_manager_test.go +++ b/subscription_manager_test.go @@ -6,6 +6,7 @@ import ( "reflect" "testing" + "github.com/pubnub/go/v7/crypto" "github.com/stretchr/testify/assert" ) @@ -569,3 +570,118 @@ func TestProcessSubscribePayloadCipherErr(t *testing.T) { <-done //pn.Destroy() } + +func TestDecryptionProcessOnEncryptedMessage(t *testing.T) { + assert := assert.New(t) + pn := NewPubNub(NewDemoConfig()) + crypto, init_err := crypto.NewAesCbcCryptoModule("enigma", true) + + assert.Nil(init_err) + + pn.Config.CryptoModule = crypto + + // Rust generated cipher text + result, err := parseCipherInterface("UE5FRAFBQ1JIEALf+E65kseYJwTw2J6BUk9MePHiCcBCS+8ykXLkBIOA", pn.Config, pn.getCryptoModule()) + + assert.Nil(err) + assert.Equal("test", result) +} + +func TestDecryptionProcessOnNoEncryptedMessage(t *testing.T) { + assert := assert.New(t) + pn := NewPubNub(NewDemoConfig()) + crypto, init_err := crypto.NewAesCbcCryptoModule("enigma", true) + + assert.Nil(init_err) + + pn.Config.CryptoModule = crypto + + result, err := parseCipherInterface("test", pn.Config, pn.getCryptoModule()) + + assert.NotNil(err) + assert.Equal("test", result) +} + +func TestProcessSubscribeWithCryptoModule(t *testing.T) { + assert := assert.New(t) + done := make(chan bool) + pn := NewPubNub(NewDemoConfig()) + + crypto, init_err := crypto.NewAesCbcCryptoModule("enigma", true) + + assert.Nil(init_err) + + pn.Config.CryptoModule = crypto + + listener := NewListener() + + go func() { + for { + select { + case result := <-listener.Message: + assert.Equal("test", result.Message) + assert.Nil(result.Error) + done <- true + break + case <-listener.Status: + case <-listener.Presence: + break + } + } + }() + + pn.AddListener(listener) + + sm := &subscribeMessage{ + Shard: "1", + SubscriptionMatch: "channel", + Channel: "channel", + Payload: "UE5FRAFBQ1JIEALf+E65kseYJwTw2J6BUk9MePHiCcBCS+8ykXLkBIOA", + } + + processSubscribePayload(pn.subscriptionManager, *sm) + <-done + //pn.Destroy() +} + +func TestProcessSubscribeWithCryptoModuleNoEncryptedMessage(t *testing.T) { + assert := assert.New(t) + done := make(chan bool) + pn := NewPubNub(NewDemoConfig()) + + crypto, init_err := crypto.NewAesCbcCryptoModule("enigma", true) + + assert.Nil(init_err) + + pn.Config.CryptoModule = crypto + + listener := NewListener() + + go func() { + for { + select { + case result := <-listener.Message: + assert.Equal("test", result.Message) + assert.NotNil(result.Error) + done <- true + break + case <-listener.Status: + case <-listener.Presence: + break + } + } + }() + + pn.AddListener(listener) + + sm := &subscribeMessage{ + Shard: "1", + SubscriptionMatch: "channel", + Channel: "channel", + Payload: "test", + } + + processSubscribePayload(pn.subscriptionManager, *sm) + <-done + //pn.Destroy() +}