From: Gulsum Atici Date: Sat, 28 Jan 2023 20:55:19 +0000 (+0300) Subject: Feature 10950: Replace pycrypto with pycryptodome X-Git-Tag: release-v14.0-start~11 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FN2VC.git;a=commitdiff_plain;h=1138656db9f4f3bce15ec609d5448474d1e0cea1 Feature 10950: Replace pycrypto with pycryptodome Remove the encryption methods from N2VC and import them from common Change-Id: Ia2c9a305a27cf6c9daaa14edab5319c735e33835 Signed-off-by: Gulsum Atici --- diff --git a/n2vc/store.py b/n2vc/store.py index cd6c6fb..e9586d7 100644 --- a/n2vc/store.py +++ b/n2vc/store.py @@ -14,15 +14,14 @@ import abc import asyncio -from base64 import b64decode -import re import typing -from Crypto.Cipher import AES from motor.motor_asyncio import AsyncIOMotorClient from n2vc.config import EnvironConfig from n2vc.vca.connection_data import ConnectionData from osm_common.dbmongo import DbMongo, DbException +from osm_common.dbbase import Encryption + DB_NAME = "osm" @@ -195,6 +194,13 @@ class MotorStore(Store): self.loop = loop or asyncio.get_event_loop() self._secret_key = None self._config = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]) + self.encryption = Encryption( + uri=uri, + config=self._config, + encoding_type="utf-8", + loop=self.loop, + logger_name="db", + ) @property def _database(self): @@ -223,7 +229,7 @@ class MotorStore(Store): data = await self._vca_collection.find_one({"_id": vca_id}) if not data: raise Exception("vca with id {} not found".format(vca_id)) - await self.decrypt_fields( + await self.encryption.decrypt_fields( data, ["secret", "cacert"], schema_version=data["schema_version"], @@ -294,114 +300,3 @@ class MotorStore(Store): async def _get_juju_info(self): """Get Juju information (the default VCA) from the admin collection""" return await self._admin_collection.find_one({"_id": "juju"}) - - # DECRYPT METHODS - async def decrypt_fields( - self, - item: dict, - fields: typing.List[str], - schema_version: str = None, - salt: str = None, - ): - """ - Decrypt fields - - Decrypt fields from a dictionary. Follows the same logic as in osm_common. - - :param: item: Dictionary with the keys to be decrypted - :param: fields: List of keys to decrypt - :param: schema version: Schema version. (i.e. 1.11) - :param: salt: Salt for the decryption - """ - flags = re.I - - async def process(_item): - if isinstance(_item, list): - for elem in _item: - await process(elem) - elif isinstance(_item, dict): - for key, val in _item.items(): - if isinstance(val, str): - if any(re.search(f, key, flags) for f in fields): - _item[key] = await self.decrypt(val, schema_version, salt) - else: - await process(val) - - await process(item) - - async def decrypt(self, value, schema_version=None, salt=None): - """ - Decrypt an encrypted value - :param value: value to be decrypted. It is a base64 string - :param schema_version: used for known encryption method used. If None or '1.0' no encryption has been done. - If '1.1' symmetric AES encryption has been done - :param salt: optional salt to be used - :return: Plain content of value - """ - await self.get_secret_key() - if not self.secret_key or not schema_version or schema_version == "1.0": - return value - else: - secret_key = self._join_secret_key(salt) - encrypted_msg = b64decode(value) - cipher = AES.new(secret_key) - decrypted_msg = cipher.decrypt(encrypted_msg) - try: - unpadded_private_msg = decrypted_msg.decode().rstrip("\0") - except UnicodeDecodeError: - raise DbException( - "Cannot decrypt information. Are you using same COMMONKEY in all OSM components?", - http_code=500, - ) - return unpadded_private_msg - - def _join_secret_key(self, update_key: typing.Any) -> bytes: - """ - Join key with secret key - - :param: update_key: str or bytes with the to update - - :return: Joined key - """ - return self._join_keys(update_key, self.secret_key) - - def _join_keys(self, key: typing.Any, secret_key: bytes) -> bytes: - """ - Join key with secret_key - - :param: key: str or bytesof the key to update - :param: secret_key: bytes of the secret key - - :return: Joined key - """ - if isinstance(key, str): - update_key_bytes = key.encode() - else: - update_key_bytes = key - new_secret_key = bytearray(secret_key) if secret_key else bytearray(32) - for i, b in enumerate(update_key_bytes): - new_secret_key[i % 32] ^= b - return bytes(new_secret_key) - - @property - def secret_key(self): - return self._secret_key - - async def get_secret_key(self): - """ - Get secret key using the database key and the serial key in the DB - The key is populated in the property self.secret_key - """ - if self.secret_key: - return - secret_key = None - if self.database_key: - secret_key = self._join_keys(self.database_key, None) - version_data = await self._admin_collection.find_one({"_id": "version"}) - if version_data and version_data.get("serial"): - secret_key = self._join_keys(b64decode(version_data["serial"]), secret_key) - self._secret_key = secret_key - - @property - def database_key(self): - return self._config["database_commonkey"] diff --git a/n2vc/tests/unit/test_store.py b/n2vc/tests/unit/test_store.py index c7aa2d6..abc5e13 100644 --- a/n2vc/tests/unit/test_store.py +++ b/n2vc/tests/unit/test_store.py @@ -138,12 +138,20 @@ class TestMotorStore(TestCase): self.vca_collection.find_one = AsyncMock() self.vca_collection.insert_one = AsyncMock() self.vca_collection.replace_one = AsyncMock() + self.encryption = Mock() + self.encryption.admin_collection = Mock() + self.encryption.admin_collection.find_one = AsyncMock() self.admin_collection = Mock() self.admin_collection.find_one = AsyncMock() self.admin_collection.insert_one = AsyncMock() self.admin_collection.replace_one = AsyncMock() self.vim_accounts_collection = Mock() self.vim_accounts_collection.find_one = AsyncMock() + self.store.encryption._client = { + "osm": { + "admin": self.encryption.admin_collection, + } + } self.store._client = { "osm": { "vca": self.vca_collection, @@ -152,7 +160,7 @@ class TestMotorStore(TestCase): } } self.store._config = {"database_commonkey": "osm"} - # self.store.decrypt_fields = Mock() + self.store.encryption._config = {"database_commonkey": "osm"} self.loop = asyncio.get_event_loop() @patch("n2vc.vca.connection_data.base64_to_cacert") @@ -174,7 +182,7 @@ class TestMotorStore(TestCase): db_find_one = conn_data.copy() db_find_one.update({"schema_version": "1.1", "_id": "id"}) self.vca_collection.find_one.return_value = db_find_one - self.store.decrypt_fields = AsyncMock() + self.store.encryption.decrypt_fields = AsyncMock() connection_data = self.loop.run_until_complete( self.store.get_vca_connection_data("vca_id") ) @@ -207,7 +215,6 @@ class TestMotorStore(TestCase): encrypted_secret = "kI46kRJh828ExSNpr16OG/q5a5/qTsE0bsHrv/W/2/g=" cacert = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ4ekNDQWx1Z0F3SUJBZ0lVRWlzTTBoQWxiYzQ0Z1ZhZWh6bS80ZUsyNnRZd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0lURU5NQXNHQTFVRUNoTUVTblZxZFRFUU1BNEdBMVVFQXhNSGFuVnFkUzFqWVRBZUZ3MHlNVEEwTWpNeApNRFV3TXpSYUZ3MHpNVEEwTWpNeE1EVTFNelJhTUNFeERUQUxCZ05WQkFvVEJFcDFhblV4RURBT0JnTlZCQU1UCkIycDFhblV0WTJFd2dnR2lNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJqd0F3Z2dHS0FvSUJnUUNhTmFvNGZab2gKTDJWYThtdy9LdCs3RG9tMHBYTlIvbEUxSHJyVmZvbmZqZFVQV01zSHpTSjJZZXlXcUNSd3BiaHlLaE82N1c1dgpUY2RsV3Y3WGFLTGtsdVkraDBZY3BQT3BFTmZZYmxrNGk0QkV1L0wzYVY5MFFkUFFrMG94S01CS2R5QlBNZVNNCkJmS2pPWXdyOGgzM0ZWUWhmVkJnMXVGZ2tGaDdTamNuNHczUFdvc1BCMjNiVHBCbGR3VE9zemN4Qm9TaDNSVTkKTzZjb3lQdDdEN0drOCtHRlA3RGRUQTdoV1RkaUM4cDBkeHp2RUNmY0psMXNFeFEyZVprS1QvVzZyelNtVDhUTApCM0ErM1FDRDhEOEVsQU1IVy9zS25SeHphYU8welpNVmVlQnRnNlFGZ1F3M0dJMGo2ZTY0K2w3VExoOW8wSkZVCjdpUitPY01xUzVDY0NROGpWV3JPSk9Xc2dEbDZ4T2FFREczYnR5SVJHY29jbVcvcEZFQjNZd1A2S1BRTUIrNXkKWDdnZExEWmFGRFBVakZmblhkMnhHdUZlMnpRTDNVbXZEUkZuUlBBaW02QlpQbWo1OFh2emFhZXROa3lyaUZLZwp4Z0Z1dVpTcDUwV2JWdjF0MkdzOTMrRE53NlhFZHRFYnlWWUNBa28xTTY0MkozczFnN3NoQnRFQ0F3RUFBYU1qCk1DRXdEZ1lEVlIwUEFRSC9CQVFEQWdLa01BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUwKQlFBRGdnR0JBRXYxM2o2ZGFVbDBqeERPSnNTV1ZJZS9JdXNXVTRpN2ZXSWlqMHAwRU1GNS9LTE8yemRndTR5SQoreVd2T3N5aVFPanEzMlRYVlo2bTRDSnBkR1dGVE5HK2lLdXVOU3M0N3g3Q3dmVUNBWm5VVzhyamd3ZWJyS3BmCkJMNEVQcTZTcW0rSmltN0VPankyMWJkY2cyUXdZb3A3eUhvaHcveWEvL0l6RTMzVzZxNHlJeEFvNDBVYUhPTEMKTGtGbnNVYitjcFZBeFlPZGp6bjFzNWhnclpuWXlETEl3WmtIdFdEWm94alUzeC9jdnZzZ1FzLytzTWYrRFU4RgpZMkJKRHJjQ1VQM2xzclc0QVpFMFplZkEwOTlncFEvb3dSN0REYnMwSjZUeFM4NGt6Tldjc1FuWnRraXZheHJNClkyVHNnaWVndFExVFdGRWpxLy9sUFV4emJCdmpnd1FBZm5CQXZGeVNKejdTa0VuVm5rUXJGaUlUQVArTHljQVIKMlg4UFI2ZGI1bEt0SitBSENDM3kvZmNQS2k0ZzNTL3djeXRRdmdvOXJ6ODRFalp5YUNTaGJXNG9jNzNrMS9RcAowQWtHRDU0ZGVDWWVPYVJNbW96c0w3ZzdxWkpFekhtODdOcVBYSy9EZFoweWNxaVFhMXY2T3QxNjdXNUlzMUkzCjBWb0IzUzloSlE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCgo=" # noqa: E501 encrypted_cacert = "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" # noqa: E501 - self.vca_collection.find_one.return_value = { "_id": "2ade7f0e-9b58-4dbd-93a3-4ec076185d39", "schema_version": "1.11", @@ -216,7 +223,7 @@ class TestMotorStore(TestCase): "secret": encrypted_secret, "cacert": encrypted_cacert, } - self.admin_collection.find_one.return_value = { + self.encryption.admin_collection.find_one.return_value = { "serial": b"l+U3HDp9td+UjQ+AN+Ypj/Uh7n3C+rMJueQNNxkIpWI=" } connection_data = self.loop.run_until_complete( diff --git a/requirements-dev.txt b/requirements-dev.txt index fc55bcb..dc58b6a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,15 +26,19 @@ kafka-python==2.0.2 # via # -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master # aiokafka +motor==1.3.1 + # via -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master osm-common @ git+https://osm.etsi.org/gerrit/osm/common.git@master # via -r requirements-dev.in packaging==23.0 # via # -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master # aiokafka -pycrypto==2.6.1 +pycryptodome==3.17 # via -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master pymongo==3.13.0 - # via -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master + # via + # -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master + # motor pyyaml==5.4.1 # via -r https://osm.etsi.org/gitweb/?p=osm/common.git;a=blob_plain;f=requirements.txt;hb=master