From 2ee30526b4a11ae6be60acce5eb554d3fb0c9999 Mon Sep 17 00:00:00 2001 From: Gordan Trevis Date: Tue, 10 Dec 2024 15:01:03 +0100 Subject: [PATCH 1/8] Adds missing rust message attribute accessibility and test --- src/pk_encryption.rs | 3 +++ tests/pk_encryption_test.py | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/pk_encryption.rs b/src/pk_encryption.rs index f93f862..2291f3f 100644 --- a/src/pk_encryption.rs +++ b/src/pk_encryption.rs @@ -13,14 +13,17 @@ use crate::{ #[pyclass] pub struct Message { /// The ciphertext of the message. + #[pyo3(get)] ciphertext: Vec, /// The message authentication code of the message. /// /// *Warning*: As stated in the module description, this does not /// authenticate the message. + #[pyo3(get)] mac: Vec, /// The ephemeral Curve25519PublicKey of the message which was used to /// derive the individual message key. + #[pyo3(get)] ephemeral_key: Vec, } diff --git a/tests/pk_encryption_test.py b/tests/pk_encryption_test.py index 39a093d..3b5c436 100644 --- a/tests/pk_encryption_test.py +++ b/tests/pk_encryption_test.py @@ -1,10 +1,17 @@ import importlib import pytest -from vodozemac import Curve25519SecretKey, Curve25519PublicKey, PkEncryption, PkDecryption, PkDecodeException +from vodozemac import ( + Curve25519SecretKey, + Curve25519PublicKey, + PkEncryption, + PkDecryption, + PkDecodeException, +) CLEARTEXT = b"test" + class TestClass(object): def test_encrypt_decrypt(self): d = PkDecryption() @@ -28,3 +35,14 @@ def test_encrypt_decrypt_with_serialized_keys(self): decoded = d.decrypt(e.encrypt(CLEARTEXT)) assert decoded == CLEARTEXT + + def test_encrypt_message_attr(self): + """Test that the Message object has accessible Python attributes (mac, ciphertext, ephemeral_key).""" + decryption = PkDecryption() + encryption = PkEncryption.from_key(decryption.public_key) + + message = encryption.encrypt(CLEARTEXT) + + assert message.mac is not None + assert message.ciphertext is not None + assert message.ephemeral_key is not None From 2f309c594b8d0e7df0cab211bb4e1e487d067145 Mon Sep 17 00:00:00 2001 From: Gordan Trevis Date: Tue, 10 Dec 2024 15:12:35 +0100 Subject: [PATCH 2/8] =?UTF-8?q?Adds=20Message=20impl=20(pymethods)=20to=20?= =?UTF-8?q?enable=20object=20creation=20from=20python.=20Adds=20python-olm?= =?UTF-8?q?=20Dependencie=20(at=C2=A03.2.16).=20Adds=20PkEncryption=20(pyt?= =?UTF-8?q?hon-olm)=C2=A0compatibility=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements-dev.txt | 1 + src/pk_encryption.rs | 12 ++++++++++ tests/pk_encryption_test.py | 47 +++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index 20faf86..d84aa7e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,3 @@ maturin pytest>=4.0 +python-olm==3.2.16 \ No newline at end of file diff --git a/src/pk_encryption.rs b/src/pk_encryption.rs index 2291f3f..60dba92 100644 --- a/src/pk_encryption.rs +++ b/src/pk_encryption.rs @@ -27,6 +27,18 @@ pub struct Message { ephemeral_key: Vec, } +#[pymethods] +impl Message { + #[new] + fn new(ciphertext: Vec, mac: Vec, ephemeral_key: Vec) -> Self { + Message { + ciphertext, + mac, + ephemeral_key, + } + } +} + /// ☣️ Compat support for libolm's PkDecryption. /// /// This implements the `m.megolm_backup.v1.curve25519-aes-sha2` described in diff --git a/tests/pk_encryption_test.py b/tests/pk_encryption_test.py index 3b5c436..7df2a3a 100644 --- a/tests/pk_encryption_test.py +++ b/tests/pk_encryption_test.py @@ -1,5 +1,7 @@ import importlib import pytest +import olm +import base64 from vodozemac import ( Curve25519SecretKey, @@ -7,6 +9,7 @@ PkEncryption, PkDecryption, PkDecodeException, + Message, ) CLEARTEXT = b"test" @@ -46,3 +49,47 @@ def test_encrypt_message_attr(self): assert message.mac is not None assert message.ciphertext is not None assert message.ephemeral_key is not None + + def test_olm_encrypt_vodo_decrypt(self): + """Test encrypting with Olm and decrypting with Vodo.""" + vodo_decryption = PkDecryption() + olm_encrypts = olm.pk.PkEncryption(vodo_decryption.public_key.to_base64()) + olm_msg = olm_encrypts.encrypt(CLEARTEXT) + + # Convert the Olm message into a Vodo message structure. + def pad_base64_decode(b64_str: str) -> bytes: + """Add the required number of '=' padding to make the length a multiple of 4, then Base64 decode.""" + return base64.b64decode(b64_str + "=" * ((4 - len(b64_str) % 4) % 4)) + + vodo_msg = Message( + pad_base64_decode(olm_msg.ciphertext), + pad_base64_decode(olm_msg.mac), + pad_base64_decode(olm_msg.ephemeral_key), + ) + + # Decrypt the message with Vodo + decrypted_plaintext = vodo_decryption.decrypt(vodo_msg) + assert decrypted_plaintext == CLEARTEXT + + def test_vodo_encrypt_olm_decrypt(self): + """Test encrypting with Vodo and decrypting with Olm.""" + olm_decryption = olm.pk.PkDecryption() + + public_key = Curve25519PublicKey.from_base64(olm_decryption.public_key) + vodo_encryption = PkEncryption.from_key(public_key) + vodo_msg = vodo_encryption.encrypt(CLEARTEXT) + + # Convert the Vodo message into an Olm message structure + def unpad_base64_encode(vodo_bytes: bytes) -> str: + """Base64 encode the given bytes and remove trailing '=' padding.""" + return base64.b64encode(vodo_bytes).decode("utf-8").rstrip("=") + + olm_msg = olm.pk.PkMessage( + unpad_base64_encode(vodo_msg.ephemeral_key), + unpad_base64_encode(vodo_msg.mac), + unpad_base64_encode(vodo_msg.ciphertext), + ) + + # Decrypt the message with Olm + decrypted_plaintext = olm_decryption.decrypt(olm_msg) + assert decrypted_plaintext.encode("utf-8") == CLEARTEXT From beee99fc2260f1e4ca57022d457fffcc889e47f8 Mon Sep 17 00:00:00 2001 From: Gordan Trevis Date: Wed, 11 Dec 2024 11:24:46 +0100 Subject: [PATCH 3/8] cargo fmt --- src/pk_encryption.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pk_encryption.rs b/src/pk_encryption.rs index 60dba92..1dc34ef 100644 --- a/src/pk_encryption.rs +++ b/src/pk_encryption.rs @@ -31,11 +31,7 @@ pub struct Message { impl Message { #[new] fn new(ciphertext: Vec, mac: Vec, ephemeral_key: Vec) -> Self { - Message { - ciphertext, - mac, - ephemeral_key, - } + Message { ciphertext, mac, ephemeral_key } } } From 461673fc375443e0b59bacabf3b3710c4420622e Mon Sep 17 00:00:00 2001 From: Gordan Trevis Date: Thu, 9 Jan 2025 11:28:43 +0100 Subject: [PATCH 4/8] Adds missing newline. --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d84aa7e..0846534 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,3 @@ maturin pytest>=4.0 -python-olm==3.2.16 \ No newline at end of file +python-olm==3.2.16 From 6ab4c2800e9290f6f50281ccc5cfe741948ec390 Mon Sep 17 00:00:00 2001 From: Gordan Trevis Date: Thu, 9 Jan 2025 11:29:24 +0100 Subject: [PATCH 5/8] Adds Missing Message Object Documentation --- src/pk_encryption.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pk_encryption.rs b/src/pk_encryption.rs index 1dc34ef..596cc7a 100644 --- a/src/pk_encryption.rs +++ b/src/pk_encryption.rs @@ -29,6 +29,15 @@ pub struct Message { #[pymethods] impl Message { + /// Create a new Message object from its components. + /// + /// This constructor creates a Message object that represents an encrypted message + /// using the `m.megolm_backup.v1.curve25519-aes-sha2` algorithm. + /// + /// # Arguments + /// * `ciphertext` - The encrypted content of the message + /// * `mac` - The message authentication code + /// * `ephemeral_key` - The ephemeral public key used during encryption #[new] fn new(ciphertext: Vec, mac: Vec, ephemeral_key: Vec) -> Self { Message { ciphertext, mac, ephemeral_key } From 9258ede94e26341a06eaf48ceaa64e0146f05896 Mon Sep 17 00:00:00 2001 From: Gordan Trevis Date: Thu, 9 Jan 2025 14:51:55 +0100 Subject: [PATCH 6/8] Adds a from_base64 message classmethod. Adds a to_base64 conversion message method. --- src/pk_encryption.rs | 58 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/src/pk_encryption.rs b/src/pk_encryption.rs index 596cc7a..9b69e67 100644 --- a/src/pk_encryption.rs +++ b/src/pk_encryption.rs @@ -1,7 +1,12 @@ use pyo3::{ - pyclass, pymethods, - types::{PyBytes, PyType}, - Bound, Py, Python, + pyclass, + pymethods, + types::{PyBytes, PyString, PyType}, + Bound, + IntoPyObject, + Py, + PyResult, + Python, }; use crate::{ @@ -42,6 +47,53 @@ impl Message { fn new(ciphertext: Vec, mac: Vec, ephemeral_key: Vec) -> Self { Message { ciphertext, mac, ephemeral_key } } + + /// Create a new Message object from unpadded Base64-encoded components. + /// + /// This function decodes the given Base64 strings and returns a `Message` + /// with the resulting byte vectors. + /// + /// # Arguments + /// * `ciphertext` - Unpadded Base64-encoded ciphertext + /// * `mac` - Unpadded Base64-encoded message authentication code + /// * `ephemeral_key` - Unpadded Base64-encoded ephemeral key + #[classmethod] + fn from_base64( + _cls: &Bound<'_, PyType>, + ciphertext: &str, + mac: &str, + ephemeral_key: &str, + ) -> Self { + let decoded_ciphertext = + vodozemac::base64_decode(ciphertext).expect("Failed to decode ciphertext"); + let decoded_mac = vodozemac::base64_decode(mac).expect("Failed to decode mac"); + let decoded_ephemeral_key = + vodozemac::base64_decode(ephemeral_key).expect("Failed to decode ephemeral_key"); + + Self { + ciphertext: decoded_ciphertext, + mac: decoded_mac, + ephemeral_key: decoded_ephemeral_key, + } + } + + /// Convert the message components to unpadded Base64-encoded strings. + /// + /// Returns a tuple of (ciphertext, mac, ephemeral_key) as unpadded Base64 strings. + fn to_base64<'py>( + &self, + py: Python<'py>, + ) -> PyResult<(Bound<'py, PyString>, Bound<'py, PyString>, Bound<'py, PyString>)> { + let ciphertext_b64 = vodozemac::base64_encode(&self.ciphertext); + let mac_b64 = vodozemac::base64_encode(&self.mac); + let ephemeral_key_b64 = vodozemac::base64_encode(&self.ephemeral_key); + + Ok(( + ephemeral_key_b64.into_pyobject(py)?, + mac_b64.into_pyobject(py)?, + ciphertext_b64.into_pyobject(py)?, + )) + } } /// ☣️ Compat support for libolm's PkDecryption. From 81fa80980140605c3d323ad326eb2ac062e2bc9c Mon Sep 17 00:00:00 2001 From: Gordan Trevis Date: Thu, 9 Jan 2025 14:53:25 +0100 Subject: [PATCH 7/8] =?UTF-8?q?Adds=C2=A0from=5Fbase64=20and=C2=A0to=5Fbas?= =?UTF-8?q?e64=20message=20functions=20to=20test.=20Removes=20base64=20pad?= =?UTF-8?q?ding=20conversion=20helper=20functions=20from=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/pk_encryption_test.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/tests/pk_encryption_test.py b/tests/pk_encryption_test.py index 7df2a3a..974e922 100644 --- a/tests/pk_encryption_test.py +++ b/tests/pk_encryption_test.py @@ -56,15 +56,10 @@ def test_olm_encrypt_vodo_decrypt(self): olm_encrypts = olm.pk.PkEncryption(vodo_decryption.public_key.to_base64()) olm_msg = olm_encrypts.encrypt(CLEARTEXT) - # Convert the Olm message into a Vodo message structure. - def pad_base64_decode(b64_str: str) -> bytes: - """Add the required number of '=' padding to make the length a multiple of 4, then Base64 decode.""" - return base64.b64decode(b64_str + "=" * ((4 - len(b64_str) % 4) % 4)) - - vodo_msg = Message( - pad_base64_decode(olm_msg.ciphertext), - pad_base64_decode(olm_msg.mac), - pad_base64_decode(olm_msg.ephemeral_key), + vodo_msg = Message.from_base64( + olm_msg.ciphertext, + olm_msg.mac, + olm_msg.ephemeral_key, ) # Decrypt the message with Vodo @@ -79,15 +74,12 @@ def test_vodo_encrypt_olm_decrypt(self): vodo_encryption = PkEncryption.from_key(public_key) vodo_msg = vodo_encryption.encrypt(CLEARTEXT) - # Convert the Vodo message into an Olm message structure - def unpad_base64_encode(vodo_bytes: bytes) -> str: - """Base64 encode the given bytes and remove trailing '=' padding.""" - return base64.b64encode(vodo_bytes).decode("utf-8").rstrip("=") - + ephemeral_key_b64, mac_b64, ciphertext_b64 = vodo_msg.to_base64() + olm_msg = olm.pk.PkMessage( - unpad_base64_encode(vodo_msg.ephemeral_key), - unpad_base64_encode(vodo_msg.mac), - unpad_base64_encode(vodo_msg.ciphertext), + ephemeral_key_b64, + mac_b64, + ciphertext_b64 ) # Decrypt the message with Olm From de4d64bac24279119a69c9a12ae49c2260188ff5 Mon Sep 17 00:00:00 2001 From: Gordan Trevis Date: Fri, 10 Jan 2025 13:19:52 +0100 Subject: [PATCH 8/8] re cargo fmt --- src/pk_encryption.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/pk_encryption.rs b/src/pk_encryption.rs index 9b69e67..3ffa37f 100644 --- a/src/pk_encryption.rs +++ b/src/pk_encryption.rs @@ -1,12 +1,7 @@ use pyo3::{ - pyclass, - pymethods, + pyclass, pymethods, types::{PyBytes, PyString, PyType}, - Bound, - IntoPyObject, - Py, - PyResult, - Python, + Bound, IntoPyObject, Py, PyResult, Python, }; use crate::{