From 9652f8840212735b66c54d2554ae11ff6d02561b Mon Sep 17 00:00:00 2001 From: realvarx Date: Mon, 9 Oct 2023 12:18:16 +0200 Subject: [PATCH 1/9] fix: Use random IV in AtClient put/get methods --- at_client/atclient.py | 16 +++++++++++----- test/encryptionutil_test.py | 5 +++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/at_client/atclient.py b/at_client/atclient.py index f196916..ae58455 100644 --- a/at_client/atclient.py +++ b/at_client/atclient.py @@ -187,7 +187,10 @@ def _put_self_key(self, key: SelfKey, value: str): key.metadata.data_signature = EncryptionUtil.sign_sha256_rsa(value, self.keys[KeysUtil.encryption_private_key_name]) try: - cipher_text = EncryptionUtil.aes_encrypt_from_base64(value, self.keys[KeysUtil.self_encryption_key_name]) + if key.metadata.iv_nonce is None: + key.metadata.iv_nonce = base64.b64encode(EncryptionUtil.generate_iv_nonce()).decode('utf-8') + + cipher_text = EncryptionUtil.aes_encrypt_from_base64(value, self.keys[KeysUtil.self_encryption_key_name], base64.b64decode(key.metadata.iv_nonce)) except Exception as e: raise AtEncryptionException(f"Failed to encrypt value with self encryption key - {e}") @@ -214,11 +217,14 @@ def _put_shared_key(self, key: SharedKey, value: str): what = "" cipher_text = None try: + if key.metadata.iv_nonce is None: + key.metadata.iv_nonce = base64.b64encode(EncryptionUtil.generate_iv_nonce()).decode('utf-8') + what = "fetch/create shared encryption key" share_to_encryption_key = self.get_encryption_key_shared_by_me(key) what = "encrypt value with shared encryption key" - cipher_text = EncryptionUtil.aes_encrypt_from_base64(value, share_to_encryption_key) + cipher_text = EncryptionUtil.aes_encrypt_from_base64(value, share_to_encryption_key, base64.b64decode(key.metadata.iv_nonce)) except Exception as e: raise AtEncryptionException(f"Failed to {what} - {e}") @@ -264,7 +270,7 @@ def _get_self_key(self, key: SelfKey): encrypted_value = fetched["data"] self_encryption_key = self.keys[KeysUtil.self_encryption_key_name] try: - decrypted_value = EncryptionUtil.aes_decrypt_from_base64(encrypted_value, self_encryption_key) + decrypted_value = EncryptionUtil.aes_decrypt_from_base64(encrypted_value, self_encryption_key, base64.b64decode(key.metadata.iv_nonce)) except Exception as e: raise AtDecryptionException(f"Failed to {command} - {e}") @@ -305,7 +311,7 @@ def _get_shared_by_me_with_other(self, shared_key: SharedKey): raise AtSecondaryConnectException(f"Failed to execute {command} - {e}") try: - return EncryptionUtil.aes_decrypt_from_base64(raw_response.get_raw_data_response(), share_encryption_key) + return EncryptionUtil.aes_decrypt_from_base64(raw_response.get_raw_data_response(), share_encryption_key, base64.b64decode(shared_key.metadata.iv_nonce)) except Exception as e: raise AtDecryptionException(f"Failed to decrypt value with shared encryption key - {e}") @@ -325,7 +331,7 @@ def _get_shared_by_other_with_me(self, shared_key:SharedKey): what = "decrypt value with shared encryption key" try: - return EncryptionUtil.aes_decrypt_from_base64(raw_response.get_raw_data_response(), share_encryption_key) + return EncryptionUtil.aes_decrypt_from_base64(raw_response.get_raw_data_response(), share_encryption_key, base64.b64decode(shared_key.metadata.iv_nonce)) except Exception as e: raise AtDecryptionException(f"Failed to {what} - {e}") diff --git a/test/encryptionutil_test.py b/test/encryptionutil_test.py index 3b45749..df48b97 100644 --- a/test/encryptionutil_test.py +++ b/test/encryptionutil_test.py @@ -8,8 +8,9 @@ def test_aes_encryption(self): """Test generating an AES key and encryption/decryption.""" secret_key = EncryptionUtil.generate_aes_key_base64() plain_text = "AES" - encrypted_text = EncryptionUtil.aes_encrypt_from_base64(plain_text, secret_key) - decrypted_text = EncryptionUtil.aes_decrypt_from_base64(encrypted_text, secret_key) + iv_nonce = EncryptionUtil.generate_iv_nonce() + encrypted_text = EncryptionUtil.aes_encrypt_from_base64(plain_text, secret_key, iv_nonce) + decrypted_text = EncryptionUtil.aes_decrypt_from_base64(encrypted_text, secret_key, iv_nonce) self.assertEqual(plain_text, decrypted_text) def test_rsa_encryption(self): From 769950a4c9f7ae217d6237edb3db03504de349cb Mon Sep 17 00:00:00 2001 From: realvarx Date: Mon, 9 Oct 2023 14:11:27 +0200 Subject: [PATCH 2/9] fix: Metadata str representation corrected --- at_client/atclient.py | 4 ++-- at_client/common/metadata.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/at_client/atclient.py b/at_client/atclient.py index ae58455..8c68515 100644 --- a/at_client/atclient.py +++ b/at_client/atclient.py @@ -188,7 +188,7 @@ def _put_self_key(self, key: SelfKey, value: str): try: if key.metadata.iv_nonce is None: - key.metadata.iv_nonce = base64.b64encode(EncryptionUtil.generate_iv_nonce()).decode('utf-8') + key.metadata.iv_nonce = base64.b64encode(EncryptionUtil.generate_iv_nonce()).rstrip().decode('utf-8') cipher_text = EncryptionUtil.aes_encrypt_from_base64(value, self.keys[KeysUtil.self_encryption_key_name], base64.b64decode(key.metadata.iv_nonce)) except Exception as e: @@ -218,7 +218,7 @@ def _put_shared_key(self, key: SharedKey, value: str): cipher_text = None try: if key.metadata.iv_nonce is None: - key.metadata.iv_nonce = base64.b64encode(EncryptionUtil.generate_iv_nonce()).decode('utf-8') + key.metadata.iv_nonce = base64.b64encode(EncryptionUtil.generate_iv_nonce()).rstrip().decode('utf-8') what = "fetch/create shared encryption key" share_to_encryption_key = self.get_encryption_key_shared_by_me(key) diff --git a/at_client/common/metadata.py b/at_client/common/metadata.py index 8557dc7..965ad82 100644 --- a/at_client/common/metadata.py +++ b/at_client/common/metadata.py @@ -127,7 +127,7 @@ def __str__(self): if self.encoding: s += f":encoding:{self.encoding}" if self.iv_nonce: - s += f":ivNonce:{binascii.b2a_base64(self.iv_nonce).decode('utf-8')[:-1]}" + s += f":ivNonce:{self.iv_nonce}" # TO?DO: Add new parameters return s From 2dc6dd3122c3d6d48140e1a200892d2564717932 Mon Sep 17 00:00:00 2001 From: realvarx Date: Mon, 9 Oct 2023 14:12:29 +0200 Subject: [PATCH 3/9] fix: I was probably thinking in Java lol --- examples/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/repl.py b/examples/repl.py index 135a327..293c3d7 100644 --- a/examples/repl.py +++ b/examples/repl.py @@ -133,7 +133,7 @@ def main(): raise Exception("Could not evaluate the key type of: " + fullKeyName) elif verb == "put": fullKeyName = parts[1] - value = command[verb.length() + fullKeyName.length() + 2:].strip() + value = command[len(verb) + len(fullKeyName) + 2:].strip() keyStringUtil = KeyStringUtil(full_key_name=fullKeyName) keyType = keyStringUtil.get_key_type() if keyType == KeyType.PUBLIC_KEY: From 0d175c94202d1dd4d8ea57449221c7f24557aaa3 Mon Sep 17 00:00:00 2001 From: realvarx Date: Wed, 11 Oct 2023 12:29:36 +0200 Subject: [PATCH 4/9] Merge trunk changes into realvarx-fix-iv-enc-dec --- at_client/common/keys.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/at_client/common/keys.py b/at_client/common/keys.py index 3b88efc..8019595 100644 --- a/at_client/common/keys.py +++ b/at_client/common/keys.py @@ -83,6 +83,10 @@ def set_time_to_live(self, ttl: int): def set_time_to_birth(self, ttb: int): self.metadata.ttb = ttb return self + + def set_iv_nonce(self, iv_nonce: str): + self.metadata.iv_nonce = iv_nonce + return self class PublicKey(AtKey): From 1776ac0fa608a88ac008e6a6922e0c511200cae3 Mon Sep 17 00:00:00 2001 From: realvarx Date: Wed, 11 Oct 2023 13:38:22 +0200 Subject: [PATCH 5/9] feat: IVNonce class --- at_client/atclient.py | 13 ++++++------ at_client/exception/atexception.py | 4 ++++ at_client/util/encryptionutil.py | 3 +-- at_client/util/iv_nonce.py | 34 ++++++++++++++++++++++++++++++ test/atclient_test.py | 16 +++++++++----- 5 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 at_client/util/iv_nonce.py diff --git a/at_client/atclient.py b/at_client/atclient.py index 8c68515..55d5ad1 100644 --- a/at_client/atclient.py +++ b/at_client/atclient.py @@ -5,6 +5,7 @@ import traceback from at_client.connections.notification.atevents import AtEvent, AtEventType +from at_client.util.iv_nonce import IVNonce from .common.atsign import AtSign @@ -188,9 +189,9 @@ def _put_self_key(self, key: SelfKey, value: str): try: if key.metadata.iv_nonce is None: - key.metadata.iv_nonce = base64.b64encode(EncryptionUtil.generate_iv_nonce()).rstrip().decode('utf-8') + raise AtMissingIVException("Missing IV for SelfKey") - cipher_text = EncryptionUtil.aes_encrypt_from_base64(value, self.keys[KeysUtil.self_encryption_key_name], base64.b64decode(key.metadata.iv_nonce)) + cipher_text = EncryptionUtil.aes_encrypt_from_base64(value, self.keys[KeysUtil.self_encryption_key_name], IVNonce.get_bytes_from_b64(key.metadata.iv_nonce)) except Exception as e: raise AtEncryptionException(f"Failed to encrypt value with self encryption key - {e}") @@ -218,13 +219,13 @@ def _put_shared_key(self, key: SharedKey, value: str): cipher_text = None try: if key.metadata.iv_nonce is None: - key.metadata.iv_nonce = base64.b64encode(EncryptionUtil.generate_iv_nonce()).rstrip().decode('utf-8') + raise AtMissingIVException("Missing IV for SharedKey") what = "fetch/create shared encryption key" share_to_encryption_key = self.get_encryption_key_shared_by_me(key) what = "encrypt value with shared encryption key" - cipher_text = EncryptionUtil.aes_encrypt_from_base64(value, share_to_encryption_key, base64.b64decode(key.metadata.iv_nonce)) + cipher_text = EncryptionUtil.aes_encrypt_from_base64(value, share_to_encryption_key, IVNonce.get_bytes_from_b64(key.metadata.iv_nonce)) except Exception as e: raise AtEncryptionException(f"Failed to {what} - {e}") @@ -311,7 +312,7 @@ def _get_shared_by_me_with_other(self, shared_key: SharedKey): raise AtSecondaryConnectException(f"Failed to execute {command} - {e}") try: - return EncryptionUtil.aes_decrypt_from_base64(raw_response.get_raw_data_response(), share_encryption_key, base64.b64decode(shared_key.metadata.iv_nonce)) + return EncryptionUtil.aes_decrypt_from_base64(raw_response.get_raw_data_response(), share_encryption_key, IVNonce.get_bytes_from_b64(shared_key.metadata.iv_nonce)) except Exception as e: raise AtDecryptionException(f"Failed to decrypt value with shared encryption key - {e}") @@ -331,7 +332,7 @@ def _get_shared_by_other_with_me(self, shared_key:SharedKey): what = "decrypt value with shared encryption key" try: - return EncryptionUtil.aes_decrypt_from_base64(raw_response.get_raw_data_response(), share_encryption_key, base64.b64decode(shared_key.metadata.iv_nonce)) + return EncryptionUtil.aes_decrypt_from_base64(raw_response.get_raw_data_response(), share_encryption_key, IVNonce.get_bytes_from_b64(shared_key.metadata.iv_nonce)) except Exception as e: raise AtDecryptionException(f"Failed to {what} - {e}") diff --git a/at_client/exception/atexception.py b/at_client/exception/atexception.py index 37a513c..34516fe 100644 --- a/at_client/exception/atexception.py +++ b/at_client/exception/atexception.py @@ -159,5 +159,9 @@ def __init__(self, message): super().__init__(message) class AtRegistrarException(AtException): + def __init__(self, message): + super().__init__(message) + +class AtMissingIVException(AtException): def __init__(self, message): super().__init__(message) \ No newline at end of file diff --git a/at_client/util/encryptionutil.py b/at_client/util/encryptionutil.py index 9497b87..4ca0406 100644 --- a/at_client/util/encryptionutil.py +++ b/at_client/util/encryptionutil.py @@ -93,5 +93,4 @@ def public_key_from_base64(s): @staticmethod def generate_iv_nonce(): - return secrets.token_bytes(16) - \ No newline at end of file + return secrets.token_bytes(16) \ No newline at end of file diff --git a/at_client/util/iv_nonce.py b/at_client/util/iv_nonce.py new file mode 100644 index 0000000..aa0913b --- /dev/null +++ b/at_client/util/iv_nonce.py @@ -0,0 +1,34 @@ +import base64 +from at_client.util.encryptionutil import EncryptionUtil + + +class IVNonce: + def __init__(self, iv_nonce_bytes=EncryptionUtil.generate_iv_nonce()): + self.iv_nonce_bytes = iv_nonce_bytes + + def __repr__(self): + return str(self) + + def __str__(self): + return self.as_b64() + + def as_b64(self): + b64_str = "" + if self.iv_nonce_bytes: + b64_str = base64.b64encode(self.iv_nonce_bytes).rstrip().decode('utf-8') + return b64_str + + def as_bytes(self): + return self.iv_nonce_bytes + + @classmethod + def from_b64(cls, b64_str: str): + return cls(base64.b64decode(b64_str)) + + def set_iv_nonce_bytes(self, iv_nonce_bytes): + self.iv_nonce_bytes = iv_nonce_bytes + return self + + @staticmethod + def get_bytes_from_b64(b64_str: str): + return base64.b64decode(b64_str) \ No newline at end of file diff --git a/test/atclient_test.py b/test/atclient_test.py index 2be4fe0..f0cd971 100644 --- a/test/atclient_test.py +++ b/test/atclient_test.py @@ -5,6 +5,7 @@ from at_client.common import AtSign from at_client.common.keys import PublicKey, SelfKey, SharedKey from at_client.exception import * +from at_client.util.iv_nonce import IVNonce from test_wrapper import skip_if_dependabot_pr class AtClientTest(unittest.TestCase): @@ -58,7 +59,8 @@ def test_put_self_key(self): """Test Put Function with Self Key""" atsign = AtSign(self.atsign1) atclient = AtClient(atsign, verbose=self.verbose) - sk = SelfKey("test_self_key", atsign) + iv = IVNonce().as_b64() + sk = SelfKey("test_self_key", atsign).set_iv_nonce(iv) response = atclient.put(sk, "test1") self.assertIsNotNone(response) @@ -68,7 +70,8 @@ def test_put_shared_key(self): shared_by = AtSign(self.atsign1) shared_with = AtSign(self.atsign2) atclient = AtClient(shared_by, verbose=self.verbose) - sk = SharedKey("test_shared_key", shared_by, shared_with) + iv = IVNonce().as_b64() + sk = SharedKey("test_shared_key", shared_by, shared_with).set_iv_nonce(iv) response = atclient.put(sk, "test1") self.assertIsNotNone(response) @@ -169,7 +172,8 @@ def test_get_self_key(self): """Test Get Function with Self Key""" atsign = AtSign(self.atsign1) atclient = AtClient(atsign, verbose=self.verbose) - sk = SelfKey("test_self_key", atsign) + iv = IVNonce().as_b64() + sk = SelfKey("test_self_key", atsign).set_iv_nonce(iv) response = atclient.put(sk, "test1") response = atclient.get(sk) self.assertEqual("test1", response) @@ -206,14 +210,16 @@ def test_get_shared_key(self): shared_by = AtSign(self.atsign1) shared_with = AtSign(self.atsign2) atclient = AtClient(shared_by, verbose=self.verbose) - sk = SharedKey("test_shared_key1445", shared_by, shared_with) + iv = IVNonce().as_b64() + sk = SharedKey("test_shared_key1445", shared_by, shared_with).set_iv_nonce(iv) atclient.put(sk, "test") response = atclient.get(sk) self.assertEqual("test", response) # Shared by other with me + iv = IVNonce().as_b64() sk = SharedKey("test_shared_key2", shared_with, shared_by) - atclient.put(SharedKey("test_shared_key2", shared_by, shared_with), "test2") + atclient.put(SharedKey("test_shared_key2", shared_by, shared_with).set_iv_nonce(iv), "test2") response = atclient.get(sk) self.assertEqual("test2", response) From a9e18f46e99d03a7ad8bf8088a32465272dce580 Mon Sep 17 00:00:00 2001 From: realvarx Date: Thu, 12 Oct 2023 11:07:52 +0200 Subject: [PATCH 6/9] fix: Check if DEPENDABOT_PR exists for local testing --- test/test_wrapper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_wrapper.py b/test/test_wrapper.py index d48fe6c..4190f7f 100644 --- a/test/test_wrapper.py +++ b/test/test_wrapper.py @@ -2,7 +2,8 @@ def skip_if_dependabot_pr(func): """Decorator for skipping a test method if it's a Dependabot PR.""" - if int(os.getenv('DEPENDABOT_PR')): + dependabot_pr = os.getenv('DEPENDABOT_PR') + if dependabot_pr is not None and int(dependabot_pr): return unittest.skip("Dependabot PR")(func) else: return func From b413a7208ba4ad9113d1752b91ac7075d5d1238a Mon Sep 17 00:00:00 2001 From: realvarx Date: Thu, 12 Oct 2023 12:19:08 +0200 Subject: [PATCH 7/9] fix: test changes 1 --- test/atclient_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/atclient_test.py b/test/atclient_test.py index f0cd971..30a077c 100644 --- a/test/atclient_test.py +++ b/test/atclient_test.py @@ -75,9 +75,10 @@ def test_put_shared_key(self): response = atclient.put(sk, "test1") self.assertIsNotNone(response) + iv = IVNonce().as_b64() shared_with = AtSign(self.atsign1) shared_by = AtSign(self.atsign2) - atclient = AtClient(shared_by, verbose=self.verbose) + atclient = AtClient(shared_by, verbose=self.verbose).set_iv_nonce(iv) sk = SharedKey("test_shared_key2", shared_by, shared_with) response = atclient.put(sk, "test2") self.assertIsNotNone(response) @@ -246,7 +247,7 @@ def test_delete_self_key(self): atsign = AtSign(self.atsign1) atclient = AtClient(atsign, verbose=self.verbose) random_key_name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)) - sk = SelfKey(random_key_name, atsign) + sk = SelfKey(random_key_name, atsign).set_iv_nonce(IVNonce().as_b64()) response = atclient.put(sk, "test1") response = atclient.delete(sk) @@ -259,7 +260,7 @@ def test_delete_shared_key(self): shared_with = AtSign(self.atsign2) atclient = AtClient(shared_by, verbose=self.verbose) random_key_name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)) - sk = SharedKey(random_key_name, shared_by, shared_with) + sk = SharedKey(random_key_name, shared_by, shared_with).set_iv_nonce(IVNonce().as_b64()) response = atclient.put(sk, "test1") response = atclient.delete(sk) From 1266c75eb52df30364823f04521582193600878a Mon Sep 17 00:00:00 2001 From: realvarx Date: Thu, 12 Oct 2023 12:29:20 +0200 Subject: [PATCH 8/9] fix: test changes 2 --- test/atclient_test.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/atclient_test.py b/test/atclient_test.py index 30a077c..cbe6bb9 100644 --- a/test/atclient_test.py +++ b/test/atclient_test.py @@ -70,16 +70,14 @@ def test_put_shared_key(self): shared_by = AtSign(self.atsign1) shared_with = AtSign(self.atsign2) atclient = AtClient(shared_by, verbose=self.verbose) - iv = IVNonce().as_b64() - sk = SharedKey("test_shared_key", shared_by, shared_with).set_iv_nonce(iv) + sk = SharedKey("test_shared_key", shared_by, shared_with).set_iv_nonce(IVNonce().as_b64()) response = atclient.put(sk, "test1") self.assertIsNotNone(response) - iv = IVNonce().as_b64() shared_with = AtSign(self.atsign1) shared_by = AtSign(self.atsign2) - atclient = AtClient(shared_by, verbose=self.verbose).set_iv_nonce(iv) - sk = SharedKey("test_shared_key2", shared_by, shared_with) + atclient = AtClient(shared_by, verbose=self.verbose) + sk = SharedKey("test_shared_key2", shared_by, shared_with).set_iv_nonce(IVNonce().as_b64()) response = atclient.put(sk, "test2") self.assertIsNotNone(response) From 6631f7280187920399d58b7e59dc99adfe011075 Mon Sep 17 00:00:00 2001 From: realvarx Date: Thu, 12 Oct 2023 15:33:55 +0200 Subject: [PATCH 9/9] fix: test changes 3 --- test/atclient_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/atclient_test.py b/test/atclient_test.py index cbe6bb9..48adde1 100644 --- a/test/atclient_test.py +++ b/test/atclient_test.py @@ -209,17 +209,17 @@ def test_get_shared_key(self): shared_by = AtSign(self.atsign1) shared_with = AtSign(self.atsign2) atclient = AtClient(shared_by, verbose=self.verbose) - iv = IVNonce().as_b64() - sk = SharedKey("test_shared_key1445", shared_by, shared_with).set_iv_nonce(iv) + sk = SharedKey("test_shared_key1445", shared_by, shared_with).set_iv_nonce(IVNonce().as_b64()) atclient.put(sk, "test") response = atclient.get(sk) self.assertEqual("test", response) # Shared by other with me iv = IVNonce().as_b64() - sk = SharedKey("test_shared_key2", shared_with, shared_by) - atclient.put(SharedKey("test_shared_key2", shared_by, shared_with).set_iv_nonce(iv), "test2") - response = atclient.get(sk) + sk1 = SharedKey("test_shared_key2", shared_with, shared_by).set_iv_nonce(iv) + sk2 = SharedKey("test_shared_key2", shared_by, shared_with).set_iv_nonce(iv) + atclient.put(sk2, "test2") + response = atclient.get(sk1) self.assertEqual("test2", response) # Shared Key not found test