Skip to content

Commit

Permalink
Fix/crypto module routing (#193)
Browse files Browse the repository at this point in the history
* Fix using CryptoModule for publish if defined

* Examples

* Replace source for old riv

* Fixes after review
  • Loading branch information
seba-aln authored Aug 13, 2024
1 parent b56d703 commit fd04298
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 35 deletions.
7 changes: 5 additions & 2 deletions examples/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def PNFactory(cipher_mode=AES.MODE_GCM, fallback_cipher_mode=AES.MODE_CBC) -> Pu
config.publish_key = getenv('PN_KEY_PUBLISH')
config.subscribe_key = getenv('PN_KEY_SUBSCRIBE')
config.secret_key = getenv('PN_KEY_SECRET')
config.cipher_key = getenv('PN_KEY_CIPHER')
config.cipher_key = getenv('PN_KEY_CIPHER', 'my_secret_cipher_key')
config.user_id = 'experiment'
config.cipher_mode = cipher_mode
config.fallback_cipher_mode = fallback_cipher_mode
Expand Down Expand Up @@ -57,5 +57,8 @@ def PNFactory(cipher_mode=AES.MODE_GCM, fallback_cipher_mode=AES.MODE_CBC) -> Pu
pubnub.crypto = PubNubCryptoModule({
PubNubAesCbcCryptor.CRYPTOR_ID: PubNubAesCbcCryptor('myCipherKey')
}, PubNubAesCbcCryptor)
encrypted = pubnub.crypto.encrypt('My Secret Text') # encrypted wih AES cryptor and `myCipherKey` cipher key

text_to_encrypt = 'My Secret Text'
encrypted = pubnub.crypto.encrypt(text_to_encrypt) # encrypted wih AES cryptor and `myCipherKey` cipher key
decrypted = pubnub.crypto.decrypt(encrypted)
print(f'Source: {text_to_encrypt}\nEncrypted: {encrypted}\nDecrypted: {decrypted}')
22 changes: 22 additions & 0 deletions examples/encrypted_publish.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from os import getenv
from pubnub.pubnub import PubNub
from pubnub.pnconfiguration import PNConfiguration
from pubnub.crypto import AesCbcCryptoModule

config = PNConfiguration()
config.publish_key = getenv('PUBLISH_KEY', 'demo')
config.subscribe_key = getenv('SUBSCRIBE_KEY', 'demo')
config.cipher_key = getenv('CIPHER_KEY', 'my_cipher_key')
config.uuid = 'example-python'
config.crypto_module = AesCbcCryptoModule(config)

pubnub = PubNub(config)

message = 'Plaintext_message'
if config.cipher_key and not config.crypto_module:
message = f'cryptodome({type(config.crypto)})'
if config.crypto_module:
message = f'crypto_module({type(config.crypto_module)})'

pubnub.publish().channel('example').message(message).sync()
print(f'published: {message}')
16 changes: 16 additions & 0 deletions examples/fetch_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

from os import getenv
from pubnub.pubnub import PubNub
from pubnub.pnconfiguration import PNConfiguration

config = PNConfiguration()
config.publish_key = getenv('PUBLISH_KEY', 'demo')
config.subscribe_key = getenv('SUBSCRIBE_KEY', 'demo')
config.cipher_key = getenv('CIPHER_KEY', 'my_cipher_key')
config.uuid = 'example'
config.cipher_key = "my_cipher_key"
pubnub = PubNub(config)

messages = pubnub.fetch_messages().channels('example').count(30).decrypt_messages().sync()
for msg in messages.result.channels['example']:
print(msg.message, f' !! Error during decryption: {msg.error}' if msg.error else '')
36 changes: 36 additions & 0 deletions examples/pubnub_asyncio/subscribe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import asyncio

from os import getenv
from pubnub.callbacks import SubscribeCallback
from pubnub.crypto import AesCbcCryptoModule
from pubnub.pnconfiguration import PNConfiguration
from pubnub.pubnub_asyncio import PubNubAsyncio

config = PNConfiguration()
config.publish_key = getenv('PUBLISH_KEY', 'demo')
config.subscribe_key = getenv('SUBSCRIBE_KEY', 'demo')
config.cipher_key = getenv('CIPHER_KEY', 'my_cipher_key')
config.crypto_module = AesCbcCryptoModule(config)
config.uuid = 'example-python'
config.enable_subscribe = True

pubnub = PubNubAsyncio(config)


class PrinterCallback(SubscribeCallback):
def status(self, pubnub, status):
print(status.category.name)

def message(self, pubnub, message):
print(message.message)


async def main():
pubnub.add_listener(PrinterCallback())
pubnub.subscribe().channels("example").execute()

await asyncio.sleep(500)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
5 changes: 2 additions & 3 deletions pubnub/crypto.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import hashlib
import json
import random
import logging

import secrets

from base64 import decodebytes, encodebytes, b64decode, b64encode
from Cryptodome.Cipher import AES
Expand Down Expand Up @@ -69,7 +68,7 @@ def extract_random_iv(self, message, use_random_iv):

def get_initialization_vector(self, use_random_iv):
if self.pubnub_configuration.use_random_initialization_vector or use_random_iv:
return "{0:016}".format(random.randint(0, 9999999999999999))
return secrets.token_urlsafe(16)[:16]
else:
return Initial16bytes

Expand Down
3 changes: 1 addition & 2 deletions pubnub/crypto_core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import hashlib
import json
import random
import secrets

from abc import abstractmethod
Expand Down Expand Up @@ -111,7 +110,7 @@ def extract_random_iv(self, message, use_random_iv):

def get_initialization_vector(self, use_random_iv) -> bytes:
if self.use_random_iv or use_random_iv:
return bytes("{0:016}".format(random.randint(0, 9999999999999999)), 'utf-8')
return secrets.token_bytes(16)
else:
return self.Initial16bytes

Expand Down
8 changes: 7 additions & 1 deletion pubnub/endpoints/fetch_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def __init__(self, pubnub):
self._include_message_actions = None
self._include_message_type = None
self._include_uuid = None
self._decrypt_messages = False

def channels(self, channels):
utils.extend_list(self._channels, channels)
Expand Down Expand Up @@ -76,6 +77,10 @@ def include_uuid(self, include_uuid):
self._include_uuid = include_uuid
return self

def decrypt_messages(self, decrypt: bool = True):
self._decrypt_messages = decrypt
return self

def custom_params(self):
params = {'max': int(self._count)}

Expand Down Expand Up @@ -155,7 +160,8 @@ def create_response(self, envelope): # pylint: disable=W0221
json_input=envelope,
include_message_actions=self._include_message_actions,
start_timetoken=self._start,
end_timetoken=self._end)
end_timetoken=self._end,
crypto_module=self.pubnub.crypto if self._decrypt_messages else None)

def request_timeout(self):
return self.pubnub.config.non_subscribe_request_timeout
Expand Down
24 changes: 15 additions & 9 deletions pubnub/endpoints/pubsub/fire.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,15 @@ def meta(self, meta):

def build_data(self):
if self._use_post is True:
cipher = self.pubnub.config.cipher_key
if cipher is not None:
return '"' + self.pubnub.config.crypto.encrypt(cipher, utils.write_value_as_string(self._message)) + '"'
else:
return utils.write_value_as_string(self._message)
stringified_message = utils.write_value_as_string(self._message)
if self.pubnub.config.crypto_module:
stringified_message = '"' + self.pubnub.config.crypto_module.encrypt(stringified_message) + '"'
elif self.pubnub.config.cipher_key is not None: # The legacy way
stringified_message = '"' + self.pubnub.config.crypto.encrypt(
self.pubnub.config.cipher_key,
stringified_message) + '"'

return stringified_message
else:
return None

Expand All @@ -67,11 +71,13 @@ def build_path(self):
self.pubnub.config.subscribe_key,
utils.url_encode(self._channel), 0)
else:
cipher = self.pubnub.config.cipher_key
stringified_message = utils.write_value_as_string(self._message)

if cipher is not None:
stringified_message = '"' + self.pubnub.config.crypto.encrypt(cipher, stringified_message) + '"'
if self.pubnub.config.crypto_module:
stringified_message = '"' + self.pubnub.config.crypto_module.encrypt(stringified_message) + '"'
elif self.pubnub.config.cipher_key is not None: # The legacy way
stringified_message = '"' + self.pubnub.config.crypto.encrypt(
self.pubnub.config.cipher_key,
stringified_message) + '"'

stringified_message = utils.url_encode(stringified_message)

Expand Down
24 changes: 15 additions & 9 deletions pubnub/endpoints/pubsub/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ def ttl(self, ttl):

def build_data(self):
if self._use_post is True:
cipher = self.pubnub.config.cipher_key
if cipher is not None:
return '"' + self.pubnub.config.crypto.encrypt(cipher, utils.write_value_as_string(self._message)) + '"'
else:
return utils.write_value_as_string(self._message)
stringified_message = utils.write_value_as_string(self._message)

if self.pubnub.config.crypto_module:
stringified_message = '"' + self.pubnub.config.crypto_module.encrypt(stringified_message) + '"'
elif self.pubnub.config.cipher_key is not None: # The legacy way
stringified_message = '"' + self.pubnub.config.crypto.encrypt(
self.pubnub.config.cipher_key,
stringified_message) + '"'
return stringified_message
else:
return None

Expand Down Expand Up @@ -99,11 +103,13 @@ def build_path(self):
self.pubnub.config.subscribe_key,
utils.url_encode(self._channel), 0)
else:
cipher = self.pubnub.config.cipher_key
stringified_message = utils.write_value_as_string(self._message)

if cipher is not None:
stringified_message = '"' + self.pubnub.config.crypto.encrypt(cipher, stringified_message) + '"'
if self.pubnub.config.crypto_module:
stringified_message = '"' + self.pubnub.config.crypto_module.encrypt(stringified_message) + '"'
elif self.pubnub.config.cipher_key is not None: # The legacy way
stringified_message = '"' + self.pubnub.config.crypto.encrypt(
self.pubnub.config.cipher_key,
stringified_message) + '"'

stringified_message = utils.url_encode(stringified_message)

Expand Down
20 changes: 16 additions & 4 deletions pubnub/models/consumer/history.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import binascii
from pubnub.exceptions import PubNubException


class PNHistoryResult(object):
Expand Down Expand Up @@ -61,7 +62,7 @@ def decrypt(self, cipher_key):

class PNFetchMessagesResult(object):

def __init__(self, channels, start_timetoken, end_timetoken):
def __init__(self, channels, start_timetoken, end_timetoken, error: Exception = None):
self.channels = channels
self.start_timetoken = start_timetoken
self.end_timetoken = end_timetoken
Expand All @@ -70,13 +71,23 @@ def __str__(self):
return "Fetch messages result for range %d..%d" % (self.start_timetoken, self.end_timetoken)

@classmethod
def from_json(cls, json_input, include_message_actions=False, start_timetoken=None, end_timetoken=None):
def from_json(cls, json_input, include_message_actions=False, start_timetoken=None, end_timetoken=None,
crypto_module=None):
channels = {}

for key, entry in json_input['channels'].items():
channels[key] = []
for item in entry:
message = PNFetchMessageItem(item['message'], item['timetoken'])
try:
error = None
item_message = crypto_module.decrypt(item['message']) if crypto_module else item['message']
except Exception as decryption_error:
if type(decryption_error) not in [PubNubException, binascii.Error, ValueError]:
raise decryption_error
item_message = item['message']
error = decryption_error

message = PNFetchMessageItem(item_message, item['timetoken'], error=error)
if 'uuid' in item:
message.uuid = item['uuid']
if 'message_type' in item:
Expand All @@ -101,11 +112,12 @@ def from_json(cls, json_input, include_message_actions=False, start_timetoken=No


class PNFetchMessageItem(object):
def __init__(self, message, timetoken, meta=None, actions=None):
def __init__(self, message, timetoken, meta=None, actions=None, error: Exception = None):
self.message = message
self.meta = meta
self.timetoken = timetoken
self.actions = actions
self.error = error

def __str__(self):
return "Fetch message item with tt: %s and content: %s" % (self.timetoken, self.message)
2 changes: 0 additions & 2 deletions pubnub/pnconfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ def file_crypto(self) -> PubNubCrypto:

@property
def crypto_module(self):
if not self._crypto_module:
self._crypto_module = self.DEFAULT_CRYPTO_MODULE(self)
return self._crypto_module

@crypto_module.setter
Expand Down
5 changes: 4 additions & 1 deletion pubnub/pubnub_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ def uuid(self):

@property
def crypto(self) -> PubNubCryptoModule:
return self.__crypto if self.__crypto else self.config.crypto_module
crypto_module = self.__crypto or self.config.crypto_module
if not crypto_module and self.config.cipher_key:
crypto_module = self.config.DEFAULT_CRYPTO_MODULE(self.config)
return crypto_module

@crypto.setter
def crypto(self, crypto: PubNubCryptoModule):
Expand Down
2 changes: 0 additions & 2 deletions scripts/run-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
os.chdir(os.path.join(REPO_ROOT))

tcmn = 'py.test tests --cov=pubnub --ignore=tests/manual/'
tcmn_ee = 'PN_ENABLE_EVENT_ENGINE=True pytest tests/integrational/asyncio/'
fcmn = 'flake8 --exclude=scripts/,src/,.cache,.git,.idea,.tox,._trial_temp/,venv/ --ignore F811,E402'


Expand All @@ -21,6 +20,5 @@ def run(command):


run(tcmn)
run(tcmn_ee)
# moved to separate action
# run(fcmn)

0 comments on commit fd04298

Please sign in to comment.