diff --git a/pubnub/crypto.py b/pubnub/crypto.py index 095c8fd2..e2da8d26 100644 --- a/pubnub/crypto.py +++ b/pubnub/crypto.py @@ -44,10 +44,15 @@ def decrypt(self, key, msg, use_random_iv=False): plain = self.depad((cipher.decrypt(extracted_message)).decode('utf-8')) except UnicodeDecodeError as e: if not self.fallback_mode: - raise e + logging.debug(f'Decryption failed: {e}') + raise PubNubException('decryption error') - cipher = AES.new(bytes(secret[0:32], "utf-8"), self.fallback_mode, initialization_vector) - plain = self.depad((cipher.decrypt(extracted_message)).decode('utf-8')) + try: + cipher = AES.new(bytes(secret[0:32], "utf-8"), self.fallback_mode, initialization_vector) + plain = self.depad((cipher.decrypt(extracted_message)).decode('utf-8')) + except (UnicodeDecodeError, ValueError, IndexError) as fallback_error: + logging.debug(f'Decryption failed: {fallback_error}') + raise PubNubException('decryption error') try: return json.loads(plain) @@ -103,11 +108,16 @@ def decrypt(self, key, file, use_random_iv=True): try: cipher = AES.new(bytes(secret[0:32], "utf-8"), self.mode, initialization_vector) result = unpad(cipher.decrypt(extracted_file), 16) - except ValueError: - if not self.fallback_mode: # No fallback mode so we return the original content - return file - cipher = AES.new(bytes(secret[0:32], "utf-8"), self.fallback_mode, initialization_vector) - result = unpad(cipher.decrypt(extracted_file), 16) + except ValueError as e: + if not self.fallback_mode: + logging.debug(f'File decryption failed: {e}') + raise PubNubException('decryption error') + try: + cipher = AES.new(bytes(secret[0:32], "utf-8"), self.fallback_mode, initialization_vector) + result = unpad(cipher.decrypt(extracted_file), 16) + except ValueError as fallback_error: + logging.debug(f'File decryption failed: {fallback_error}') + raise PubNubException('decryption error') return result diff --git a/pubnub/crypto_core.py b/pubnub/crypto_core.py index 33b38bbe..073530cc 100644 --- a/pubnub/crypto_core.py +++ b/pubnub/crypto_core.py @@ -1,5 +1,6 @@ import hashlib import json +import logging import secrets from abc import abstractmethod @@ -79,15 +80,24 @@ def decrypt(self, payload: CryptorPayload, key=None, use_random_iv=False, binary raise PubNubException('decryption error') cipher = AES.new(bytes(secret[0:32], "utf-8"), self.mode, initialization_vector) if binary_mode: - return self.depad(cipher.decrypt(extracted_message), binary_mode) + try: + return self.depad(cipher.decrypt(extracted_message), binary_mode) + except (ValueError, IndexError) as e: + logging.debug(f'Decryption failed: {e}') + raise PubNubException('decryption error') try: plain = self.depad((cipher.decrypt(extracted_message)).decode('utf-8'), binary_mode) except UnicodeDecodeError as e: if not self.fallback_mode: - raise e + logging.debug(f'Decryption failed: {e}') + raise PubNubException('decryption error') - cipher = AES.new(bytes(secret[0:32], "utf-8"), self.fallback_mode, initialization_vector) - plain = self.depad((cipher.decrypt(extracted_message)).decode('utf-8'), binary_mode) + try: + cipher = AES.new(bytes(secret[0:32], "utf-8"), self.fallback_mode, initialization_vector) + plain = self.depad((cipher.decrypt(extracted_message)).decode('utf-8'), binary_mode) + except (UnicodeDecodeError, ValueError, IndexError) as fallback_error: + logging.debug(f'Decryption failed: {fallback_error}') + raise PubNubException('decryption error') try: return json.loads(plain) @@ -156,7 +166,11 @@ def decrypt(self, payload: CryptorPayload, key=None, binary_mode: bool = False, cipher = AES.new(secret, mode=self.mode, iv=iv) - if binary_mode: - return unpad(cipher.decrypt(payload['data']), AES.block_size) - else: - return unpad(cipher.decrypt(payload['data']), AES.block_size).decode() + try: + if binary_mode: + return unpad(cipher.decrypt(payload['data']), AES.block_size) + else: + return unpad(cipher.decrypt(payload['data']), AES.block_size).decode() + except (ValueError, UnicodeDecodeError) as e: + logging.debug(f'Decryption failed: {e}') + raise PubNubException('decryption error') diff --git a/pubnub/workers.py b/pubnub/workers.py index cf72c948..6a2ae8ff 100644 --- a/pubnub/workers.py +++ b/pubnub/workers.py @@ -47,7 +47,7 @@ def _process_message(self, message_input): try: return self._pubnub.crypto.decrypt(message_input), None except Exception as exception: - logger.warning("could not decrypt message: \"%s\", due to error %s" % (message_input, str(exception))) + logger.warning("could not decrypt message, due to error %s" % str(exception)) pn_status = PNStatus() pn_status.category = PNStatusCategory.PNDecryptionErrorCategory diff --git a/tests/integrational/fixtures/asyncio/history/delete_success.yaml b/tests/integrational/fixtures/asyncio/history/delete_success.yaml index 55ecb204..1eda2fc2 100644 --- a/tests/integrational/fixtures/asyncio/history/delete_success.yaml +++ b/tests/integrational/fixtures/asyncio/history/delete_success.yaml @@ -23,12 +23,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - https - - ps.pndsn.com - - /v3/history/sub-key/sub-c-mock-key/channel/my-ch - - start=123&end=456&pnsdk=PubNub-Python-Asyncio%2F4.7.0&uuid=af9429d0-aa10-4919-9670-abe67a5c395f - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/history/delete_with_space_and_wildcard_in_channel_name.yaml b/tests/integrational/fixtures/asyncio/history/delete_with_space_and_wildcard_in_channel_name.yaml index 0bfd695e..c2cf4ba6 100644 --- a/tests/integrational/fixtures/asyncio/history/delete_with_space_and_wildcard_in_channel_name.yaml +++ b/tests/integrational/fixtures/asyncio/history/delete_with_space_and_wildcard_in_channel_name.yaml @@ -23,12 +23,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - https - - ps.pndsn.com - - /v3/history/sub-key/sub-c-mock-key/channel/my-ch-%20%7C.%2A%20%24 - - start=123&end=456&pnsdk=PubNub-Python-Asyncio%2F4.7.0&uuid=fbbfbfbf-2b08-4561-bccb-3a0003b0b71b - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/members/get_members.yaml b/tests/integrational/fixtures/asyncio/members/get_members.yaml index 58df946c..36b5f9aa 100644 --- a/tests/integrational/fixtures/asyncio/members/get_members.yaml +++ b/tests/integrational/fixtures/asyncio/members/get_members.yaml @@ -20,12 +20,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/spaces/value1/users - - count=True&include=custom,user,user.custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=f032239c-241a-45f7-ac74-02ebfe06a29e - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/members/get_space_memberships.yaml b/tests/integrational/fixtures/asyncio/members/get_space_memberships.yaml index 5c9ea488..ec2bf0be 100644 --- a/tests/integrational/fixtures/asyncio/members/get_space_memberships.yaml +++ b/tests/integrational/fixtures/asyncio/members/get_space_memberships.yaml @@ -20,12 +20,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/users/mg3/spaces - - count=True&include=custom,space,space.custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=8d72a1a1-eec4-4b3f-84d6-53e88c80ded1 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/members/update_members.yaml b/tests/integrational/fixtures/asyncio/members/update_members.yaml index b91f865d..463ad87d 100644 --- a/tests/integrational/fixtures/asyncio/members/update_members.yaml +++ b/tests/integrational/fixtures/asyncio/members/update_members.yaml @@ -21,12 +21,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/spaces/value1/users - - include=custom,user,user.custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=8cc8fb7d-6bb8-4109-a6b9-750490d89e7a - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/members/update_space_memberships.yaml b/tests/integrational/fixtures/asyncio/members/update_space_memberships.yaml index a1226005..1aca722b 100644 --- a/tests/integrational/fixtures/asyncio/members/update_space_memberships.yaml +++ b/tests/integrational/fixtures/asyncio/members/update_space_memberships.yaml @@ -20,12 +20,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/users/mg/spaces - - include=custom,space,space.custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=ca41ef07-c7db-4874-be1d-7039c919ef6f - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/message_count/multi.yaml b/tests/integrational/fixtures/asyncio/message_count/multi.yaml index 00cf604b..c9aff29e 100644 --- a/tests/integrational/fixtures/asyncio/message_count/multi.yaml +++ b/tests/integrational/fixtures/asyncio/message_count/multi.yaml @@ -11,11 +11,6 @@ interactions: Cache-Control: no-cache, Connection: keep-alive, Content-Length: '30', Content-Type: text/javascript; charset="UTF-8", Date: 'Sun, 24 Feb 2019 20:13:16 GMT'} status: {code: 200, message: OK} - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult [http, balancer1g.bronze.aws-pdx-1.ps.pn, - /publish/demo-36/demo-36/0/unique_asyncio_1/0/%22something%22, pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=d2a546ca-037c-499a-9d87-35951bbbd289, - ''] - request: body: null headers: @@ -30,10 +25,4 @@ interactions: Content-Length: '109', Content-Type: text/javascript; charset="UTF-8", Date: 'Sun, 24 Feb 2019 20:13:16 GMT', Server: Pubnub} status: {code: 200, message: OK} - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult [http, balancer1g.bronze.aws-pdx-1.ps.pn, - '/v3/history/sub-key/demo-36/message-counts/unique_asyncio_1,unique_asyncio_2', - 'channelsTimetoken=15510391962937046,15510391962937046&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=d2a546ca-037c-499a-9d87-35951bbbd289&l_pub=0.37061548233032227', - ''] version: 1 diff --git a/tests/integrational/fixtures/asyncio/message_count/single.yaml b/tests/integrational/fixtures/asyncio/message_count/single.yaml index d3f599a3..c6674df2 100644 --- a/tests/integrational/fixtures/asyncio/message_count/single.yaml +++ b/tests/integrational/fixtures/asyncio/message_count/single.yaml @@ -11,11 +11,6 @@ interactions: Cache-Control: no-cache, Connection: keep-alive, Content-Length: '30', Content-Type: text/javascript; charset="UTF-8", Date: 'Sun, 24 Feb 2019 20:13:15 GMT'} status: {code: 200, message: OK} - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult [http, balancer1g.bronze.aws-pdx-1.ps.pn, - /publish/demo-36/demo-36/0/unique_asyncio/0/%22bla%22, pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=68f7b4f4-c169-4a49-b09d-7c68e22049b8, - ''] - request: body: null headers: @@ -30,9 +25,4 @@ interactions: Content-Length: '86', Content-Type: text/javascript; charset="UTF-8", Date: 'Sun, 24 Feb 2019 20:13:15 GMT', Server: Pubnub} status: {code: 200, message: OK} - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult [http, balancer1g.bronze.aws-pdx-1.ps.pn, - /v3/history/sub-key/demo-36/message-counts/unique_asyncio, timetoken=15510391957007172&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=68f7b4f4-c169-4a49-b09d-7c68e22049b8&l_pub=0.4618048667907715, - ''] version: 1 diff --git a/tests/integrational/fixtures/asyncio/pam/global_level.yaml b/tests/integrational/fixtures/asyncio/pam/global_level.yaml index 4d3902af..b6b8630d 100644 --- a/tests/integrational/fixtures/asyncio/pam/global_level.yaml +++ b/tests/integrational/fixtures/asyncio/pam/global_level.yaml @@ -22,14 +22,6 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - https - - ps.pndsn.com - - /v2/auth/grant/sub-key/sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f - - pnsdk=PubNub-Python-Asyncio%2F4.7.0&r=1&signature=v2.Um4OSe_f8tRtFo2tuw0lmwE6Rq5wgjTHmfblkIyoZ4I×tamp=1606303468&uuid=my_uuid&w=1 - - '' - request: body: null headers: @@ -53,12 +45,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - https - - ps.pndsn.com - - /v2/auth/grant/sub-key/sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f - - g=0&j=0&l_pam=0.24709081649780273&m=0&pnsdk=PubNub-Python-Asyncio%2F4.7.0&r=0&signature=v2.NyyRFAQKOOpqAAAMlcN6wHg-cmHLwC6L7KgdEqwS7bY×tamp=1606303468&u=0&uuid=my_uuid&w=0 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/pam/multiple_channel_groups.yaml b/tests/integrational/fixtures/asyncio/pam/multiple_channel_groups.yaml index 52d4b9ec..b622e2b5 100644 --- a/tests/integrational/fixtures/asyncio/pam/multiple_channel_groups.yaml +++ b/tests/integrational/fixtures/asyncio/pam/multiple_channel_groups.yaml @@ -22,12 +22,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v2/auth/grant/sub-key/sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f - - channel-group=test-pam-asyncio-cg1,test-pam-asyncio-cg2&pnsdk=PubNub-Python-Asyncio%2F4.1.0&r=1&signature=v2.0eTFy_Kgi-Qiz6nD3NmfZlu4Z4ndtUT5pYHl57imcZI×tamp=1577189139&uuid=my_uuid&w=1 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/pam/multiple_channel_groups_with_auth.yaml b/tests/integrational/fixtures/asyncio/pam/multiple_channel_groups_with_auth.yaml index 96415701..81d78c89 100644 --- a/tests/integrational/fixtures/asyncio/pam/multiple_channel_groups_with_auth.yaml +++ b/tests/integrational/fixtures/asyncio/pam/multiple_channel_groups_with_auth.yaml @@ -22,12 +22,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v2/auth/grant/sub-key/sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f - - auth=test-pam-asyncio-auth&channel-group=test-pam-asyncio-cg1,test-pam-asyncio-cg2&pnsdk=PubNub-Python-Asyncio%2F4.1.0&r=1&signature=v2.pjnKPVphocAsl8BsxLeirCZMbNVzNOQV3CS6mfm1Bbc×tamp=1577189139&uuid=my_uuid&w=1 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/pam/multiple_channels.yaml b/tests/integrational/fixtures/asyncio/pam/multiple_channels.yaml index e2f5ed42..2baf24dd 100644 --- a/tests/integrational/fixtures/asyncio/pam/multiple_channels.yaml +++ b/tests/integrational/fixtures/asyncio/pam/multiple_channels.yaml @@ -22,12 +22,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v2/auth/grant/sub-key/sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f - - channel=test-pam-asyncio-ch1,test-pam-asyncio-ch2&pnsdk=PubNub-Python-Asyncio%2F4.1.0&r=1&signature=v2.z01_vYcxRHcQlLohU41PTYPzZOfaU8xWK4qXRF4bjK8×tamp=1577189138&uuid=test-pam-asyncio-uuid&w=1 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/pam/multiple_channels_with_auth.yaml b/tests/integrational/fixtures/asyncio/pam/multiple_channels_with_auth.yaml index bc6d41d9..14beeb1c 100644 --- a/tests/integrational/fixtures/asyncio/pam/multiple_channels_with_auth.yaml +++ b/tests/integrational/fixtures/asyncio/pam/multiple_channels_with_auth.yaml @@ -22,12 +22,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v2/auth/grant/sub-key/sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f - - auth=test-pam-asyncio-auth&channel=test-pam-asyncio-ch1,test-pam-asyncio-ch2&pnsdk=PubNub-Python-Asyncio%2F4.1.0&r=1&signature=v2.YX_q8cliqGK-cMPUevjVQ1rRnEFAkKLLkutGJt9X1OY×tamp=1577189138&uuid=my_uuid&w=1 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/pam/single_channel.yaml b/tests/integrational/fixtures/asyncio/pam/single_channel.yaml index a1fc9401..40a26f3c 100644 --- a/tests/integrational/fixtures/asyncio/pam/single_channel.yaml +++ b/tests/integrational/fixtures/asyncio/pam/single_channel.yaml @@ -22,12 +22,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v2/auth/grant/sub-key/sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f - - channel=test-pam-asyncio-ch&pnsdk=PubNub-Python-Asyncio%2F4.1.0&r=1&signature=v2.fNqcroTl6ykcSUYDgrOmpGVe2b_11FKkOjU8_LMt7E8×tamp=1577189138&uuid=my_uuid&w=1 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/pam/single_channel_group.yaml b/tests/integrational/fixtures/asyncio/pam/single_channel_group.yaml index 6afa8d61..42a2f4c2 100644 --- a/tests/integrational/fixtures/asyncio/pam/single_channel_group.yaml +++ b/tests/integrational/fixtures/asyncio/pam/single_channel_group.yaml @@ -22,12 +22,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v2/auth/grant/sub-key/sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f - - channel-group=test-pam-asyncio-cg&pnsdk=PubNub-Python-Asyncio%2F4.1.0&r=1&signature=v2.BihlEpGJOoGHtVcTzIw1h0Jp7vqKoIdpkxaIYrvV1FU×tamp=1577189138&uuid=test-pam-asyncio-uuid&w=1 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/pam/single_channel_group_with_auth.yaml b/tests/integrational/fixtures/asyncio/pam/single_channel_group_with_auth.yaml index 936af4a5..26752a85 100644 --- a/tests/integrational/fixtures/asyncio/pam/single_channel_group_with_auth.yaml +++ b/tests/integrational/fixtures/asyncio/pam/single_channel_group_with_auth.yaml @@ -22,12 +22,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v2/auth/grant/sub-key/sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f - - auth=test-pam-asyncio-auth&channel-group=test-pam-asyncio-cg&pnsdk=PubNub-Python-Asyncio%2F4.1.0&r=1&signature=v2.Sjk_iz1y4xns4Mt3xuch3EWqgAJogNU6RJ6TCce-_3w×tamp=1577189139&uuid=test-pam-asyncio-uuid&w=1 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/pam/single_channel_with_auth.yaml b/tests/integrational/fixtures/asyncio/pam/single_channel_with_auth.yaml index 559f522f..f9f6ca65 100644 --- a/tests/integrational/fixtures/asyncio/pam/single_channel_with_auth.yaml +++ b/tests/integrational/fixtures/asyncio/pam/single_channel_with_auth.yaml @@ -22,12 +22,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v2/auth/grant/sub-key/sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f - - auth=test-pam-asyncio-auth&channel=test-pam-asyncio-ch&pnsdk=PubNub-Python-Asyncio%2F4.1.0&r=1&signature=v2.P1WnlQZkBoiO8ah1YE9CS_Cgq4Iyi34TmjCB9Hj0qUA×tamp=1577189138&uuid=test-pam-asyncio-uuid&w=1 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/signal/single.yaml b/tests/integrational/fixtures/asyncio/signal/single.yaml index a5af2fa2..68c67607 100644 --- a/tests/integrational/fixtures/asyncio/signal/single.yaml +++ b/tests/integrational/fixtures/asyncio/signal/single.yaml @@ -21,12 +21,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /signal/demo/demo/0/unique_sync/0/%22test%22 - - pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=f5706789-e3a0-459e-871d-e4aed46e5458 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/space/create_space.yaml b/tests/integrational/fixtures/asyncio/space/create_space.yaml index c910a4bd..c222fdea 100644 --- a/tests/integrational/fixtures/asyncio/space/create_space.yaml +++ b/tests/integrational/fixtures/asyncio/space/create_space.yaml @@ -18,12 +18,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/spaces - - include=custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=30b485f7-38c6-4e5b-8911-06f5016d415d - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/space/delete_space.yaml b/tests/integrational/fixtures/asyncio/space/delete_space.yaml index cb4fd9c2..bbb514e2 100644 --- a/tests/integrational/fixtures/asyncio/space/delete_space.yaml +++ b/tests/integrational/fixtures/asyncio/space/delete_space.yaml @@ -18,12 +18,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/spaces/in_space - - pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=982fa2bc-479b-4f37-a3a0-1a44f7a00011 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/space/get_space.yaml b/tests/integrational/fixtures/asyncio/space/get_space.yaml index 20d03d79..44d22b79 100644 --- a/tests/integrational/fixtures/asyncio/space/get_space.yaml +++ b/tests/integrational/fixtures/asyncio/space/get_space.yaml @@ -18,12 +18,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/spaces/in_space - - include=custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=51ba448e-4a65-424f-a1ec-27fb53cf6d6d - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/space/get_spaces.yaml b/tests/integrational/fixtures/asyncio/space/get_spaces.yaml index c4b37ebe..0ed86974 100644 --- a/tests/integrational/fixtures/asyncio/space/get_spaces.yaml +++ b/tests/integrational/fixtures/asyncio/space/get_spaces.yaml @@ -20,12 +20,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/spaces - - include=custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=bcf17a92-51a3-4ede-ad5c-975f84ccc235 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/space/update_space.yaml b/tests/integrational/fixtures/asyncio/space/update_space.yaml index 5d46e81b..15cc0f5f 100644 --- a/tests/integrational/fixtures/asyncio/space/update_space.yaml +++ b/tests/integrational/fixtures/asyncio/space/update_space.yaml @@ -18,12 +18,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/spaces/in_space - - include=custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=116bf7ab-5fa8-4735-8f23-3b458cfc336f - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/user/create_user.yaml b/tests/integrational/fixtures/asyncio/user/create_user.yaml index 90247789..8b753a5d 100644 --- a/tests/integrational/fixtures/asyncio/user/create_user.yaml +++ b/tests/integrational/fixtures/asyncio/user/create_user.yaml @@ -18,12 +18,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/users - - include=custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=9065de8e-c73a-4fd8-80bc-a59644b08df8 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/user/delete_user.yaml b/tests/integrational/fixtures/asyncio/user/delete_user.yaml index 9181ce4e..58d6874f 100644 --- a/tests/integrational/fixtures/asyncio/user/delete_user.yaml +++ b/tests/integrational/fixtures/asyncio/user/delete_user.yaml @@ -18,12 +18,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/users/mg - - pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=1e0a67ef-817e-4f95-a90d-c089b4f6f8d8 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/user/fetch_user.yaml b/tests/integrational/fixtures/asyncio/user/fetch_user.yaml index 7a0fe6d3..f2a0dd93 100644 --- a/tests/integrational/fixtures/asyncio/user/fetch_user.yaml +++ b/tests/integrational/fixtures/asyncio/user/fetch_user.yaml @@ -18,12 +18,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/users/mg - - include=custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=16409448-274c-4414-be17-da487e2f3798 - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/user/update_user.yaml b/tests/integrational/fixtures/asyncio/user/update_user.yaml index 142447c8..f74d166c 100644 --- a/tests/integrational/fixtures/asyncio/user/update_user.yaml +++ b/tests/integrational/fixtures/asyncio/user/update_user.yaml @@ -18,12 +18,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/users/mg - - include=custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=9a39324e-5c80-4d99-952e-565748cc858d - - '' version: 1 diff --git a/tests/integrational/fixtures/asyncio/user/users_get.yaml b/tests/integrational/fixtures/asyncio/user/users_get.yaml index d87357e9..8ad0752a 100644 --- a/tests/integrational/fixtures/asyncio/user/users_get.yaml +++ b/tests/integrational/fixtures/asyncio/user/users_get.yaml @@ -20,12 +20,4 @@ interactions: status: code: 200 message: OK - url: !!python/object/new:yarl.URL - state: !!python/tuple - - !!python/object/new:urllib.parse.SplitResult - - http - - ps.pndsn.com - - /v1/objects/demo/users - - include=custom&pnsdk=PubNub-Python-Asyncio%2F4.1.0&uuid=1055f507-e325-4537-994b-cbcdbffb9b80 - - '' version: 1 diff --git a/tests/unit/test_crypto.py b/tests/unit/test_crypto.py index c2171ba3..765f669b 100644 --- a/tests/unit/test_crypto.py +++ b/tests/unit/test_crypto.py @@ -1,7 +1,10 @@ +import pytest + from pubnub.pubnub import PubNub from pubnub.pnconfiguration import PNConfiguration -from pubnub.crypto import PubNubCryptodome, PubNubCrypto, AesCbcCryptoModule, PubNubCryptoModule -from pubnub.crypto_core import PubNubAesCbcCryptor, PubNubLegacyCryptor +from pubnub.crypto import PubNubCryptodome, PubNubCrypto, PubNubFileCrypto, AesCbcCryptoModule, \ + PubNubCryptoModule +from pubnub.crypto_core import PubNubAesCbcCryptor, PubNubLegacyCryptor, CryptorPayload from pubnub.exceptions import PubNubException from tests.helper import pnconf_file_copy, hardcoded_iv_config_copy, pnconf_env_copy @@ -213,3 +216,89 @@ def test_php_encrypted_crosscheck(self): "mvPmbuQKLErBzS2l7vEohCwbmAJODPR2yNhJGB8989reTZ7Y7Q==" decrypted = crypto.decrypt(phpmess) assert decrypted == 'PHP can into space with headers and aes cbc and other shiny stuff' + + +class TestDecryptionFailureRaises: + """Decrypt failures must raise PubNubException, never silently return ciphertext.""" + + cipher_key = 'myCipherKey' + + @pytest.mark.parametrize('use_random_iv', [True, False]) + def test_file_decrypt_wrong_key_raises(self, use_random_iv): + config = pnconf_file_copy() + config.cipher_key = self.cipher_key + config.use_random_initialization_vector = use_random_iv + crypto = PubNubFileCrypto(config) + encrypted = crypto.encrypt(self.cipher_key, b'secret file content', use_random_iv=use_random_iv) + + with pytest.raises(PubNubException) as exc_info: + crypto.decrypt('totallyWrongKey', encrypted, use_random_iv=use_random_iv) + assert 'decryption error' in str(exc_info.value) + assert exc_info.value is not encrypted + + def test_module_decrypt_file_corrupt_raises(self): + # The AES-CBC cryptor validates PKCS#7 padding, so corrupting the ciphertext + # is detected and surfaces as a generic decryption error. + config = pnconf_file_copy() + config.cipher_key = self.cipher_key + module = AesCbcCryptoModule(config) + encrypted = module.encrypt_file(b'secret file content') + corrupted = bytearray(encrypted) + corrupted[-1] ^= 0xFF + + with pytest.raises(PubNubException) as exc_info: + module.decrypt_file(bytes(corrupted)) + assert 'decryption error' in str(exc_info.value) + + +class TestPaddingOracleHardening: + """Distinct crypto failures must produce an identical + generic error, leaking no PyCryptodome mode-specific message.""" + + cipher_key = 'myCipherKey' + _leak_substrings = ('padding', 'pkcs', 'boundary', 'block', 'incorrect') + + def _bad_padding_payload(self, cryptor): + # Flip a byte in the first ciphertext block to corrupt the decrypted PKCS#7 + # padding while keeping the input block-aligned (so unpad, not decrypt, fails). + payload = cryptor.encrypt(b'A' * 20) + corrupted = bytearray(payload['data']) + corrupted[15] ^= 0xFF + + return CryptorPayload(data=bytes(corrupted), cryptor_data=payload['cryptor_data']) + + def _wrong_length_payload(self, cryptor): + payload = cryptor.encrypt(b'A' * 20) + return CryptorPayload(data=payload['data'][:-3], cryptor_data=payload['cryptor_data']) + + def _assert_generic(self, message): + assert 'decryption error' in message + lowered = message.lower() + + for leak in self._leak_substrings: + assert leak not in lowered, f"error message leaks crypto detail: {message!r}" + + @pytest.mark.parametrize('binary_mode', [True, False]) + def test_aes_cbc_failures_identical(self, binary_mode): + cryptor = PubNubAesCbcCryptor(self.cipher_key) + + with pytest.raises(PubNubException) as bad_padding: + cryptor.decrypt(self._bad_padding_payload(cryptor), binary_mode=binary_mode) + with pytest.raises(PubNubException) as wrong_length: + cryptor.decrypt(self._wrong_length_payload(cryptor), binary_mode=binary_mode) + + self._assert_generic(str(bad_padding.value)) + self._assert_generic(str(wrong_length.value)) + + assert str(bad_padding.value) == str(wrong_length.value) + + def test_cryptodome_message_wrong_key(self): + config = pnconf_file_copy() + config.cipher_key = self.cipher_key + config.use_random_initialization_vector = True + crypto = PubNubCryptodome(config) + encrypted = crypto.encrypt(self.cipher_key, 'a secret message', use_random_iv=True) + + with pytest.raises(PubNubException) as exc_info: + crypto.decrypt('totallyWrongKey', encrypted, use_random_iv=True) + self._assert_generic(str(exc_info.value)) diff --git a/tests/unit/test_crypto_module.py b/tests/unit/test_crypto_module.py index 6cf60268..58da38e7 100644 --- a/tests/unit/test_crypto_module.py +++ b/tests/unit/test_crypto_module.py @@ -14,10 +14,13 @@ - CryptoHeader and CryptorPayload (data structures) """ +import pytest + from pubnub.crypto_core import ( PubNubCrypto, CryptorPayload, PubNubCryptor, PubNubLegacyCryptor, PubNubAesCbcCryptor ) +from pubnub.exceptions import PubNubException from pubnub.pnconfiguration import PNConfiguration @@ -1381,14 +1384,11 @@ def test_unicode_error_handling(self): binary_data = bytes([0xFF, 0xFE, 0xFD, 0xFC] * 4) encrypted = cryptor.encrypt(binary_data) - try: - # Try to decrypt as text (non-binary mode) - result = cryptor.decrypt(encrypted, binary_mode=False) - # If no exception, should handle gracefully - assert result is not None - except (UnicodeDecodeError, ValueError) as e: - # Expected for invalid UTF-8 - assert isinstance(e, (UnicodeDecodeError, ValueError)) + # Decrypting non-utf-8 content as text (no fallback mode) surfaces a single + # generic decryption error rather than leaking the raw UnicodeDecodeError. + with pytest.raises(PubNubException) as exc_info: + cryptor.decrypt(encrypted, binary_mode=False) + assert 'decryption error' in str(exc_info.value) def test_json_parsing_error_handling(self): """Test handling of JSON parsing errors.""" diff --git a/tests/unit/test_file_encryption.py b/tests/unit/test_file_encryption.py index 52275460..ed8306aa 100644 --- a/tests/unit/test_file_encryption.py +++ b/tests/unit/test_file_encryption.py @@ -3,6 +3,7 @@ from pubnub.pubnub import PubNub from pubnub.crypto import PubNubFileCrypto, AesCbcCryptoModule, LegacyCryptoModule +from pubnub.exceptions import PubNubException from Cryptodome.Cipher import AES from tests.helper import pnconf_file_copy @@ -99,23 +100,24 @@ def test_encrypt_decrypt_different_cipher_modes(self): assert encrypted_cbc != encrypted_gcm def test_decrypt_with_wrong_key(self): - """Test decryption with wrong cipher key.""" + """Test decryption with wrong cipher key raises instead of returning ciphertext.""" encrypted_data = self.file_crypto.encrypt(self.cipher_key, self.test_data) - - # Try to decrypt with wrong key - should return original encrypted data wrong_key = 'wrongKey' - result = self.file_crypto.decrypt(wrong_key, encrypted_data) - # With wrong key, should return the original encrypted data - assert result == encrypted_data + with pytest.raises(PubNubException) as exc_info: + self.file_crypto.decrypt(wrong_key, encrypted_data) + + assert 'decryption error' in str(exc_info.value) def test_decrypt_invalid_data(self): - """Test decryption of invalid/corrupted data.""" + """Test decryption of invalid/corrupted data raises instead of returning ciphertext.""" invalid_data = b'this is not encrypted data' - # Should return the original data when decryption fails - result = self.file_crypto.decrypt(self.cipher_key, invalid_data) - assert result == invalid_data + # Should raise a generic decryption error when decryption fails + with pytest.raises(PubNubException) as exc_info: + self.file_crypto.decrypt(self.cipher_key, invalid_data) + + assert 'decryption error' in str(exc_info.value) def test_fallback_cipher_mode(self): """Test fallback cipher mode functionality.""" @@ -264,10 +266,10 @@ def test_file_encryption_with_different_keys(self, file_for_upload): # Encrypt with key1 encrypted_with_key1 = pubnub1.crypto.encrypt_file(file_content) - # Try to decrypt with key2 (should fail gracefully) + # Try to decrypt with key2. The default (legacy) crypto module has no padding + # integrity check, so a wrong key yields garbage rather than raising - but it + # must never return the original content. decrypted_with_wrong_key = pubnub2.crypto.decrypt_file(encrypted_with_key1) - - # Should return empty bytes when decryption fails with wrong key assert decrypted_with_wrong_key != file_content # Decrypt with correct key