Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-http-ece for openSUSE:Factory 
checked in at 2026-03-23 17:15:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-http-ece (Old)
 and      /work/SRC/openSUSE:Factory/.python-http-ece.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-http-ece"

Mon Mar 23 17:15:24 2026 rev:6 rq:1342009 version:1.2.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-http-ece/python-http-ece.changes  
2025-05-30 17:25:52.756629977 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-http-ece.new.8177/python-http-ece.changes    
    2026-03-23 17:17:26.975859113 +0100
@@ -1,0 +2,6 @@
+Mon Mar 23 10:33:47 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.2.1:
+  * Switch to pytest
+
+-------------------------------------------------------------------

Old:
----
  http_ece-1.1.0.tar.gz
  test_ece.py

New:
----
  http_ece-1.2.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-http-ece.spec ++++++
--- /var/tmp/diff_new_pack.Brv3dW/_old  2026-03-23 17:17:27.487880409 +0100
+++ /var/tmp/diff_new_pack.Brv3dW/_new  2026-03-23 17:17:27.487880409 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-http-ece
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,15 +17,14 @@
 
 
 Name:           python-http-ece
-Version:        1.1.0
+Version:        1.2.1
 Release:        0
 Summary:        Encrypted Content Encoding for HTTP
 License:        MIT
 Group:          Development/Languages/Python
 URL:            https://github.com/martinthomson/encrypted-content-encoding
 Source:         
https://files.pythonhosted.org/packages/source/h/http_ece/http_ece-%{version}.tar.gz
-Source1:        
https://raw.githubusercontent.com/web-push-libs/encrypted-content-encoding/v%{version}/python/http_ece/tests/test_ece.py
-Source2:        
https://raw.githubusercontent.com/web-push-libs/encrypted-content-encoding/master/LICENSE
+Source1:        
https://raw.githubusercontent.com/web-push-libs/encrypted-content-encoding/master/LICENSE
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module wheel}
@@ -44,7 +43,7 @@
 
 %prep
 %setup -q -n http_ece-%{version}
-cp %{SOURCE1} %{SOURCE2} .
+cp %{SOURCE1} .
 
 %build
 %pyproject_wheel

++++++ http_ece-1.1.0.tar.gz -> http_ece-1.2.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/http_ece-1.1.0/MANIFEST.in 
new/http_ece-1.2.1/MANIFEST.in
--- old/http_ece-1.1.0/MANIFEST.in      2015-11-10 22:56:51.000000000 +0100
+++ new/http_ece-1.2.1/MANIFEST.in      2024-01-09 01:42:58.000000000 +0100
@@ -1 +1,3 @@
 include README.rst
+include tox.ini
+recursive-include http_ece/tests *
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/http_ece-1.1.0/PKG-INFO new/http_ece-1.2.1/PKG-INFO
--- old/http_ece-1.1.0/PKG-INFO 2019-02-11 22:37:49.000000000 +0100
+++ new/http_ece-1.2.1/PKG-INFO 2024-08-08 02:10:46.566744800 +0200
@@ -1,17 +1,24 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: http_ece
-Version: 1.1.0
+Version: 1.2.1
 Summary: Encrypted Content Encoding for HTTP
 Home-page: https://github.com/martinthomson/encrypted-content-encoding
 Author: Martin Thomson
 Author-email: [email protected]
 License: MIT
-Description: Encipher HTTP Messages
 Keywords: crypto http
-Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Requires-Dist: cryptography>=2.5
+
+Encipher HTTP Messages
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/http_ece-1.1.0/http_ece/__init__.py 
new/http_ece-1.2.1/http_ece/__init__.py
--- old/http_ece-1.1.0/http_ece/__init__.py     2019-02-11 22:28:29.000000000 
+0100
+++ new/http_ece-1.2.1/http_ece/__init__.py     2024-01-09 01:42:58.000000000 
+0100
@@ -6,12 +6,8 @@
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives import hashes
 from cryptography.hazmat.primitives.kdf.hkdf import HKDF
-from cryptography.hazmat.primitives.ciphers import (
-    Cipher, algorithms, modes
-)
-from cryptography.hazmat.primitives.serialization import (
-    Encoding, PublicFormat
-)
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
 from cryptography.hazmat.primitives.asymmetric import ec
 
 MAX_RECORD_SIZE = pow(2, 31) - 1
@@ -30,13 +26,14 @@
 
 class ECEException(Exception):
     """Exception for ECE encryption functions"""
+
     def __init__(self, message):
         self.message = message
 
 
-def derive_key(mode, version, salt, key,
-               private_key, dh, auth_secret,
-               keyid, keylabel="P-256"):
+def derive_key(
+    mode, version, salt, key, private_key, dh, auth_secret, keyid, 
keylabel="P-256"
+):
     """Derive the encryption key
 
     :param mode: operational mode (encrypt or decrypt)
@@ -69,20 +66,16 @@
     def derive_dh(mode, version, private_key, dh, keylabel):
         def length_prefix(key):
             return struct.pack("!H", len(key)) + key
+
         if isinstance(dh, ec.EllipticCurvePublicKey):
             pubkey = dh
-            dh = dh.public_bytes(
-                Encoding.X962,
-                PublicFormat.UncompressedPoint)
+            dh = dh.public_bytes(Encoding.X962, PublicFormat.UncompressedPoint)
         else:
-            pubkey = ec.EllipticCurvePublicKey.from_encoded_point(
-                ec.SECP256R1(),
-                dh
-            )
+            pubkey = 
ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), dh)
 
         encoded = private_key.public_key().public_bytes(
-            Encoding.X962,
-            PublicFormat.UncompressedPoint)
+            Encoding.X962, PublicFormat.UncompressedPoint
+        )
         if mode == "encrypt":
             sender_pub_key = encoded
             receiver_pub_key = dh
@@ -93,29 +86,36 @@
         if version == "aes128gcm":
             context = b"WebPush: info\x00" + receiver_pub_key + sender_pub_key
         else:
-            context = (keylabel.encode('utf-8') + b"\0" +
-                       length_prefix(receiver_pub_key) +
-                       length_prefix(sender_pub_key))
+            context = (
+                keylabel.encode("utf-8")
+                + b"\0"
+                + length_prefix(receiver_pub_key)
+                + length_prefix(sender_pub_key)
+            )
 
         return private_key.exchange(ec.ECDH(), pubkey), context
 
     if version not in versions:
-        raise ECEException(u"Invalid version")
-    if mode not in ['encrypt', 'decrypt']:
-        raise ECEException(u"unknown 'mode' specified: " + mode)
+        raise ECEException("Invalid version")
+    if mode not in ["encrypt", "decrypt"]:
+        raise ECEException("unknown 'mode' specified: " + mode)
     if salt is None or len(salt) != KEY_LENGTH:
-        raise ECEException(u"'salt' must be a 16 octet value")
+        raise ECEException("'salt' must be a 16 octet value")
     if dh is not None:
         if private_key is None:
-            raise ECEException(u"DH requires a private_key")
-        (secret, context) = derive_dh(mode=mode, version=version,
-                                      private_key=private_key, dh=dh,
-                                      keylabel=keylabel)
+            raise ECEException("DH requires a private_key")
+        (secret, context) = derive_dh(
+            mode=mode,
+            version=version,
+            private_key=private_key,
+            dh=dh,
+            keylabel=keylabel,
+        )
     else:
         secret = key
 
     if secret is None:
-        raise ECEException(u"unable to determine the secret")
+        raise ECEException("unable to determine the secret")
 
     if version == "aesgcm":
         keyinfo = build_info(b"aesgcm", context)
@@ -134,13 +134,13 @@
         if version == "aes128gcm":
             info = context
         else:
-            info = build_info(b'auth', b'')
+            info = build_info(b"auth", b"")
         hkdf_auth = HKDF(
             algorithm=hashes.SHA256(),
             length=32,
             salt=auth_secret,
             info=info,
-            backend=default_backend()
+            backend=default_backend(),
         )
         secret = hkdf_auth.derive(secret)
 
@@ -149,32 +149,38 @@
         length=KEY_LENGTH,
         salt=salt,
         info=keyinfo,
-        backend=default_backend()
+        backend=default_backend(),
     )
     hkdf_nonce = HKDF(
         algorithm=hashes.SHA256(),
         length=NONCE_LENGTH,
         salt=salt,
         info=nonceinfo,
-        backend=default_backend()
+        backend=default_backend(),
     )
     return hkdf_key.derive(secret), hkdf_nonce.derive(secret)
 
 
 def iv(base, counter):
-    """Generate an initialization vector.
-
-    """
+    """Generate an initialization vector."""
     if (counter >> 64) != 0:
-        raise ECEException(u"Counter too big")
+        raise ECEException("Counter too big")
     (mask,) = struct.unpack("!Q", base[4:])
     return base[:4] + struct.pack("!Q", counter ^ mask)
 
 
-def decrypt(content, salt=None, key=None,
-            private_key=None, dh=None, auth_secret=None,
-            keyid=None, keylabel="P-256",
-            rs=4096, version="aes128gcm"):
+def decrypt(
+    content,
+    salt=None,
+    key=None,
+    private_key=None,
+    dh=None,
+    auth_secret=None,
+    keyid=None,
+    keylabel="P-256",
+    rs=4096,
+    version="aes128gcm",
+):
     """
     Decrypt a data block
 
@@ -200,6 +206,7 @@
     :rtype str
 
     """
+
     def parse_content_header(content):
         """Parse an aes128gcm content body and extract the header values.
 
@@ -211,79 +218,86 @@
         return {
             "salt": content[:16],
             "rs": struct.unpack("!L", content[16:20])[0],
-            "keyid": content[21:21 + id_len],
-            "content": content[21 + id_len:],
+            "keyid": content[21 : 21 + id_len],
+            "content": content[21 + id_len :],
         }
 
     def decrypt_record(key, nonce, counter, content):
         decryptor = Cipher(
             algorithms.AES(key),
             modes.GCM(iv(nonce, counter), tag=content[-TAG_LENGTH:]),
-            backend=default_backend()
+            backend=default_backend(),
         ).decryptor()
         return decryptor.update(content[:-TAG_LENGTH]) + decryptor.finalize()
 
     def unpad_legacy(data):
-        pad_size = versions[version]['pad']
+        pad_size = versions[version]["pad"]
         pad = functools.reduce(
-            lambda x, y: x << 8 | y, struct.unpack(
-                "!" + ("B" * pad_size), data[0:pad_size])
+            lambda x, y: x << 8 | y,
+            struct.unpack("!" + ("B" * pad_size), data[0:pad_size]),
         )
-        if pad_size + pad > len(data) or \
-           data[pad_size:pad_size+pad] != (b"\x00" * pad):
-            raise ECEException(u"Bad padding")
-        return data[pad_size + pad:]
+        if pad_size + pad > len(data) or data[pad_size : pad_size + pad] != (
+            b"\x00" * pad
+        ):
+            raise ECEException("Bad padding")
+        return data[pad_size + pad :]
 
     def unpad(data, last):
         i = len(data) - 1
         for i in range(len(data) - 1, -1, -1):
-            v = struct.unpack('B', data[i:i+1])[0]
+            v = struct.unpack("B", data[i : i + 1])[0]
             if v != 0:
                 if not last and v != 1:
-                    raise ECEException(u'record delimiter != 1')
+                    raise ECEException("record delimiter != 1")
                 if last and v != 2:
-                    raise ECEException(u'last record delimiter != 2')
+                    raise ECEException("last record delimiter != 2")
                 return data[0:i]
-        raise ECEException(u'all zero record plaintext')
+        raise ECEException("all zero record plaintext")
 
     if version not in versions:
-        raise ECEException(u"Invalid version")
+        raise ECEException("Invalid version")
 
-    overhead = versions[version]['pad']
+    overhead = versions[version]["pad"]
     if version == "aes128gcm":
         try:
             content_header = parse_content_header(content)
         except Exception:
             raise ECEException("Could not parse the content header")
-        salt = content_header['salt']
-        rs = content_header['rs']
-        keyid = content_header['keyid']
+        salt = content_header["salt"]
+        rs = content_header["rs"]
+        keyid = content_header["keyid"]
         if private_key is not None and not dh:
             dh = keyid
         else:
-            keyid = keyid.decode('utf-8')
-        content = content_header['content']
+            keyid = keyid.decode("utf-8")
+        content = content_header["content"]
         overhead += 16
 
-    (key_, nonce_) = derive_key("decrypt", version=version,
-                                salt=salt, key=key,
-                                private_key=private_key, dh=dh,
-                                auth_secret=auth_secret,
-                                keyid=keyid, keylabel=keylabel)
+    (key_, nonce_) = derive_key(
+        "decrypt",
+        version=version,
+        salt=salt,
+        key=key,
+        private_key=private_key,
+        dh=dh,
+        auth_secret=auth_secret,
+        keyid=keyid,
+        keylabel=keylabel,
+    )
     if rs <= overhead:
-        raise ECEException(u"Record size too small")
+        raise ECEException("Record size too small")
     chunk = rs
     if version != "aes128gcm":
         chunk += 16  # account for tags in old versions
         if len(content) % chunk == 0:
-            raise ECEException(u"Message truncated")
+            raise ECEException("Message truncated")
 
-    result = b''
+    result = b""
     counter = 0
     try:
         for i in list(range(0, len(content), chunk)):
-            data = decrypt_record(key_, nonce_, counter, content[i:i + chunk])
-            if version == 'aes128gcm':
+            data = decrypt_record(key_, nonce_, counter, content[i : i + 
chunk])
+            if version == "aes128gcm":
                 last = (i + chunk) >= len(content)
                 result += unpad(data, last)
             else:
@@ -294,10 +308,18 @@
     return result
 
 
-def encrypt(content, salt=None, key=None,
-            private_key=None, dh=None, auth_secret=None,
-            keyid=None, keylabel="P-256",
-            rs=4096, version="aes128gcm"):
+def encrypt(
+    content,
+    salt=None,
+    key=None,
+    private_key=None,
+    dh=None,
+    auth_secret=None,
+    keyid=None,
+    keylabel="P-256",
+    rs=4096,
+    version="aes128gcm",
+):
     """
     Encrypt a data block
 
@@ -323,17 +345,18 @@
     :rtype str
 
     """
+
     def encrypt_record(key, nonce, counter, buf, last):
         encryptor = Cipher(
             algorithms.AES(key),
             modes.GCM(iv(nonce, counter)),
-            backend=default_backend()
+            backend=default_backend(),
         ).encryptor()
 
-        if version == 'aes128gcm':
-            data = encryptor.update(buf + (b'\x02' if last else b'\x01'))
+        if version == "aes128gcm":
+            data = encryptor.update(buf + (b"\x02" if last else b"\x01"))
         else:
-            data = encryptor.update((b"\x00" * versions[version]['pad']) + buf)
+            data = encryptor.update((b"\x00" * versions[version]["pad"]) + buf)
         data += encryptor.finalize()
         data += encryptor.tag
         return data
@@ -363,25 +386,31 @@
         return header + content
 
     if version not in versions:
-        raise ECEException(u"Invalid version")
+        raise ECEException("Invalid version")
 
     if salt is None:
         salt = os.urandom(16)
 
-    (key_, nonce_) = derive_key("encrypt", version=version,
-                                salt=salt, key=key,
-                                private_key=private_key, dh=dh,
-                                auth_secret=auth_secret,
-                                keyid=keyid, keylabel=keylabel)
+    (key_, nonce_) = derive_key(
+        "encrypt",
+        version=version,
+        salt=salt,
+        key=key,
+        private_key=private_key,
+        dh=dh,
+        auth_secret=auth_secret,
+        keyid=keyid,
+        keylabel=keylabel,
+    )
 
-    overhead = versions[version]['pad']
-    if version == 'aes128gcm':
+    overhead = versions[version]["pad"]
+    if version == "aes128gcm":
         overhead += 16
         end = len(content)
     else:
         end = len(content) + 1
     if rs <= overhead:
-        raise ECEException(u"Record size too small")
+        raise ECEException("Record size too small")
     chunk_size = rs - overhead
 
     result = b""
@@ -390,16 +419,16 @@
     # the extra one on the loop ensures that we produce a padding only
     # record if the data length is an exact multiple of the chunk size
     for i in list(range(0, end, chunk_size)):
-        result += encrypt_record(key_, nonce_, counter,
-                                 content[i:i + chunk_size],
-                                 (i + chunk_size) >= end)
+        result += encrypt_record(
+            key_, nonce_, counter, content[i : i + chunk_size], (i + 
chunk_size) >= end
+        )
         counter += 1
     if version == "aes128gcm":
         if keyid is None and private_key is not None:
             kid = private_key.public_key().public_bytes(
-                Encoding.X962,
-                PublicFormat.UncompressedPoint)
+                Encoding.X962, PublicFormat.UncompressedPoint
+            )
         else:
-            kid = (keyid or '').encode('utf-8')
+            kid = (keyid or "").encode("utf-8")
         return compose_aes128gcm(salt, result, rs, keyid=kid)
     return result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/http_ece-1.1.0/http_ece/tests/test_ece.py 
new/http_ece-1.2.1/http_ece/tests/test_ece.py
--- old/http_ece-1.1.0/http_ece/tests/test_ece.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/http_ece-1.2.1/http_ece/tests/test_ece.py       2024-08-08 
02:06:24.000000000 +0200
@@ -0,0 +1,496 @@
+import base64
+import json
+import os
+import struct
+import unittest
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
+
+from pytest import raises
+
+import http_ece as ece
+from http_ece import ECEException
+
+
+TEST_VECTORS = os.path.join(os.sep, "..", "encrypt_data.json")[1:]
+
+
+def logmsg(arg):
+    """
+    print(arg)
+    """
+    return
+
+
+def logbuf(msg, buf):
+    """used for debugging test code."""
+    if buf is None:
+        buf = b""
+    logmsg(msg + ": [" + str(len(buf)) + "]")
+    for i in list(range(0, len(buf), 48)):
+        logmsg("    " + repr(buf[i : i + 48]))
+    return
+
+
+def b64e(arg):
+    if arg is None:
+        return None
+    return base64.urlsafe_b64encode(arg).decode()
+
+
+def b64d(arg):
+    if arg is None:
+        return None
+    return base64.urlsafe_b64decode(str(arg) + "===="[: len(arg) % 4 :])
+
+
+def make_key():
+    return ec.generate_private_key(ec.SECP256R1(), default_backend())
+
+
+class TestEce(unittest.TestCase):
+    def setUp(self):
+        self.private_key = make_key()
+        self.dh = self.private_key.public_key().public_bytes(
+            Encoding.X962, PublicFormat.UncompressedPoint
+        )
+        self.m_key = os.urandom(16)
+        self.m_salt = os.urandom(16)
+
+    def test_derive_key_invalid_mode(self):
+        with raises(ECEException) as ex:
+            ece.derive_key(
+                "invalid",
+                version="aes128gcm",
+                salt=self.m_salt,
+                key=self.m_key,
+                private_key=self.private_key,
+                dh=None,
+                auth_secret=None,
+                keyid="valid",
+            )
+        assert ex.value.message == "unknown 'mode' specified: invalid"
+
+    def test_derive_key_invalid_salt(self):
+        with raises(ECEException) as ex:
+            ece.derive_key(
+                "encrypt",
+                version="aes128gcm",
+                salt=None,
+                key=self.m_key,
+                private_key=self.private_key,
+                dh=None,
+                auth_secret=None,
+                keyid="valid",
+            )
+        assert ex.value.message == "'salt' must be a 16 octet value"
+
+    def test_derive_key_invalid_version(self):
+        with raises(ECEException) as ex:
+            ece.derive_key(
+                "encrypt",
+                version="invalid",
+                salt=self.m_salt,
+                key=None,
+                private_key=self.private_key,
+                dh=None,
+                auth_secret=None,
+                keyid="valid",
+            )
+        assert ex.value.message == "Invalid version"
+
+    def test_derive_key_no_private_key(self):
+        with raises(ECEException) as ex:
+            ece.derive_key(
+                "encrypt",
+                version="aes128gcm",
+                salt=self.m_salt,
+                key=None,
+                private_key=None,
+                dh=self.dh,
+                auth_secret=None,
+                keyid="valid",
+            )
+        assert ex.value.message == "DH requires a private_key"
+
+    def test_derive_key_no_secret(self):
+        with raises(ECEException) as ex:
+            ece.derive_key(
+                "encrypt",
+                version="aes128gcm",
+                salt=self.m_salt,
+                key=None,
+                private_key=None,
+                dh=None,
+                auth_secret=None,
+                keyid="valid",
+            )
+        assert ex.value.message == "unable to determine the secret"
+
+    def test_iv_bad_counter(self):
+        with raises(ECEException) as ex:
+            ece.iv(os.urandom(8), pow(2, 64) + 1)
+        assert ex.value.message == "Counter too big"
+
+
+class TestEceChecking(unittest.TestCase):
+    def setUp(self):
+        self.m_key = os.urandom(16)
+        self.m_input = os.urandom(5)
+        # This header is specific to the padding tests, but can be used
+        # elsewhere
+        self.m_header = b"\xaa\xd2\x05}3S\xb7\xff7\xbd\xe4*\xe1\xd5\x0f\xda"
+        self.m_header += struct.pack("!L", 32) + b"\0"
+
+    def test_encrypt_small_rs(self):
+        with raises(ECEException) as ex:
+            ece.encrypt(
+                self.m_input,
+                version="aes128gcm",
+                key=self.m_key,
+                rs=1,
+            )
+        assert ex.value.message == "Record size too small"
+
+    def test_decrypt_small_rs(self):
+        header = os.urandom(16) + struct.pack("!L", 2) + b"\0"
+        with raises(ECEException) as ex:
+            ece.decrypt(
+                header + self.m_input,
+                version="aes128gcm",
+                key=self.m_key,
+                rs=1,
+            )
+        assert ex.value.message == "Record size too small"
+
+    def test_encrypt_bad_version(self):
+        with raises(ECEException) as ex:
+            ece.encrypt(
+                self.m_input,
+                version="bogus",
+                key=self.m_key,
+            )
+        assert ex.value.message == "Invalid version"
+
+    def test_decrypt_bad_version(self):
+        with raises(ECEException) as ex:
+            ece.decrypt(
+                self.m_input,
+                version="bogus",
+                key=self.m_key,
+            )
+        assert ex.value.message == "Invalid version"
+
+    def test_decrypt_bad_header(self):
+        with raises(ECEException) as ex:
+            ece.decrypt(
+                os.urandom(4),
+                version="aes128gcm",
+                key=self.m_key,
+            )
+        assert ex.value.message == "Could not parse the content header"
+
+    def test_encrypt_long_keyid(self):
+        with raises(ECEException) as ex:
+            ece.encrypt(
+                self.m_input,
+                version="aes128gcm",
+                key=self.m_key,
+                keyid=b64e(os.urandom(192)),  # 256 bytes
+            )
+        assert ex.value.message == "keyid is too long"
+
+    def test_overlong_padding(self):
+        with raises(ECEException) as ex:
+            ece.decrypt(
+                self.m_header + b"\xbb\xc7\xb9ev\x0b\xf0f+\x93\xf4"
+                b"\xe5\xd6\x94\xb7e\xf0\xcd\x15\x9b(\x01\xa5",
+                version="aes128gcm",
+                key=b"d\xc7\x0ed\xa7%U\x14Q\xf2\x08\xdf\xba\xa0\xb9r",
+                keyid=b64e(os.urandom(192)),  # 256 bytes
+            )
+        assert ex.value.message == "all zero record plaintext"
+
+    def test_bad_early_delimiter(self):
+        with raises(ECEException) as ex:
+            ece.decrypt(
+                self.m_header + b"\xb9\xc7\xb9ev\x0b\xf0\x9eB\xb1\x08C8u"
+                b"\xa3\x06\xc9x\x06\n\xfc|}\xe9R\x85\x91"
+                b"\x8bX\x02`\xf3"
+                + b"E8z(\xe5%f/H\xc1\xc32\x04\xb1\x95\xb5N\x9ep\xd4\x0e<\xf3"
+                b"\xef\x0cg\x1b\xe0\x14I~\xdc",
+                version="aes128gcm",
+                key=b"d\xc7\x0ed\xa7%U\x14Q\xf2\x08\xdf\xba\xa0\xb9r",
+                keyid=b64e(os.urandom(192)),  # 256 bytes
+            )
+        assert ex.value.message == "record delimiter != 1"
+
+    def test_bad_final_delimiter(self):
+        with raises(ECEException) as ex:
+            ece.decrypt(
+                self.m_header + b"\xba\xc7\xb9ev\x0b\xf0\x9eB\xb1\x08Ji"
+                b"\xe4P\x1b\x8dI\xdb\xc6y#MG\xc2W\x16",
+                version="aes128gcm",
+                key=b"d\xc7\x0ed\xa7%U\x14Q\xf2\x08\xdf\xba\xa0\xb9r",
+                keyid=b64e(os.urandom(192)),  # 256 bytes
+            )
+        assert ex.value.message == "last record delimiter != 2"
+
+    def test_damage(self):
+        with raises(ECEException) as ex:
+            ece.decrypt(
+                self.m_header + b"\xbb\xc6\xb1\x1dF:~\x0f\x07+\xbe\xaaD"
+                b"\xe0\xd6.K\xe5\xf9]%\xe3\x86q\xe0}",
+                version="aes128gcm",
+                key=b"d\xc7\x0ed\xa7%U\x14Q\xf2\x08\xdf\xba\xa0\xb9r",
+                keyid=b64e(os.urandom(192)),  # 256 bytes
+            )
+        assert ex.value.message == "Decryption error: InvalidTag()"
+
+
+class TestEceIntegration(unittest.TestCase):
+    def setUp(self):
+        ece.keys = {}
+        ece.labels = {}
+
+    def tearDown(self):
+        ece.keys = {}
+        ece.labels = {}
+
+    def _rsoverhead(self, version):
+        if version == "aesgcm128":
+            return 1
+        if version == "aesgcm":
+            return 2
+        return 18
+
+    def _generate_input(self, minLen=0):
+        length = struct.unpack("!B", os.urandom(1))[0] + minLen
+        return os.urandom(length)
+
+    def encrypt_decrypt(self, input, encrypt_params, decrypt_params=None, 
version=None):
+        """Run and encrypt/decrypt cycle on some test data
+
+        :param input: data for input
+        :type length: bytearray
+        :param encrypt_params: Dictionary of encryption parameters
+        :type encrypt_params: dict
+        :param decrypt_params: Optional dictionary of decryption parameters
+        :type decrypt_params: dict
+        :param version: Content-Type of the body, formulating encryption
+        :type enumerate("aes128gcm", "aesgcm", "aesgcm128"):
+        """
+        if decrypt_params is None:
+            decrypt_params = encrypt_params
+        logbuf("Input", input)
+        if "key" in encrypt_params:
+            logbuf("Key", encrypt_params["key"])
+        if version != "aes128gcm":
+            salt = os.urandom(16)
+            decrypt_rs_default = 4096
+        else:
+            salt = None
+            decrypt_rs_default = None
+        logbuf("Salt", salt)
+        if "auth_secret" in encrypt_params:
+            logbuf("Auth Secret", encrypt_params["auth_secret"])
+        encrypted = ece.encrypt(
+            input,
+            salt=salt,
+            key=encrypt_params.get("key"),
+            keyid=encrypt_params.get("keyid"),
+            dh=encrypt_params.get("dh"),
+            private_key=encrypt_params.get("private_key"),
+            auth_secret=encrypt_params.get("auth_secret"),
+            rs=encrypt_params.get("rs", 4096),
+            version=version,
+        )
+        logbuf("Encrypted", encrypted)
+        decrypted = ece.decrypt(
+            encrypted,
+            salt=salt,
+            key=decrypt_params.get("key"),
+            keyid=decrypt_params.get("keyid"),
+            dh=decrypt_params.get("dh"),
+            private_key=decrypt_params.get("private_key"),
+            auth_secret=decrypt_params.get("auth_secret"),
+            rs=decrypt_params.get("rs", decrypt_rs_default),
+            version=version,
+        )
+        logbuf("Decrypted", decrypted)
+        assert input == decrypted
+
+    def use_explicit_key(self, version=None):
+        params = {
+            "key": os.urandom(16),
+        }
+        self.encrypt_decrypt(self._generate_input(), params, version=version)
+
+    def auth_secret(self, version):
+        params = {"key": os.urandom(16), "auth_secret": os.urandom(16)}
+        self.encrypt_decrypt(self._generate_input(), params, version=version)
+
+    def exactly_one_record(self, version=None):
+        input = self._generate_input(1)
+        params = {"key": os.urandom(16), "rs": len(input) + 
self._rsoverhead(version)}
+        self.encrypt_decrypt(input, params, version=version)
+
+    def detect_truncation(self, version):
+        if version == "aes128gcm":
+            return
+
+        input = self._generate_input(2)
+        key = os.urandom(16)
+        salt = os.urandom(16)
+
+        rs = len(input) + self._rsoverhead(version) - 1
+        encrypted = ece.encrypt(input, salt=salt, key=key, rs=rs, 
version=version)
+        if version == "aes128gcm":
+            chunk = encrypted[0 : 21 + rs]
+        else:
+            chunk = encrypted[0 : rs + 16]
+        with raises(ECEException) as ex:
+            ece.decrypt(chunk, salt=salt, key=key, rs=rs, version=version)
+        assert ex.value.message == "Message truncated"
+
+    def use_dh(self, version):
+        def pubbytes(k):
+            return k.public_key().public_bytes(
+                Encoding.X962, PublicFormat.UncompressedPoint
+            )
+
+        def privbytes(k):
+            d = k.private_numbers().private_value
+            b = b""
+            for i in range(0, 
k.private_numbers().public_numbers.curve.key_size, 32):
+                b = struct.pack("!L", (d >> i) & 0xFFFFFFFF) + b
+            return b
+
+        def logec(s, k):
+            logbuf(s + " private", privbytes(k))
+            logbuf(s + " public", pubbytes(k))
+
+        def is_uncompressed(k):
+            b1 = pubbytes(k)[0:1]
+            assert struct.unpack("B", b1)[0] == 4, "is an uncompressed point"
+
+        # the static key is used by the receiver
+        static_key = make_key()
+        is_uncompressed(static_key)
+
+        logec("receiver", static_key)
+
+        # the ephemeral key is used by the sender
+        ephemeral_key = make_key()
+        is_uncompressed(ephemeral_key)
+
+        logec("sender", ephemeral_key)
+
+        auth_secret = os.urandom(16)
+
+        if version != "aes128gcm":
+            decrypt_dh = pubbytes(ephemeral_key)
+        else:
+            decrypt_dh = None
+
+        encrypt_params = {
+            "private_key": ephemeral_key,
+            "dh": static_key.public_key(),
+            "auth_secret": auth_secret,
+        }
+        decrypt_params = {
+            "private_key": static_key,
+            "dh": decrypt_dh,
+            "auth_secret": auth_secret,
+        }
+
+        self.encrypt_decrypt(
+            self._generate_input(), encrypt_params, decrypt_params, version
+        )
+
+    def test_types(self):
+        for ver in ["aes128gcm", "aesgcm", "aesgcm128"]:
+            for f in (
+                self.use_dh,
+                self.use_explicit_key,
+                self.auth_secret,
+                self.exactly_one_record,
+                self.detect_truncation,
+            ):
+                ece.keys = {}
+                ece.labels = {}
+                f(version=ver)
+
+
+class TestNode(unittest.TestCase):
+    """Testing using data from the node.js version."""
+
+    def setUp(self):
+        if not os.path.exists(TEST_VECTORS):
+            self.skipTest("No %s file found" % TEST_VECTORS)
+        f = open(TEST_VECTORS, "r")
+        self.legacy_data = json.loads(f.read())
+        f.close()
+
+    def _run(self, mode):
+        if mode == "encrypt":
+            func = ece.encrypt
+            local = "sender"
+            inp = "input"
+            outp = "encrypted"
+        else:
+            func = ece.decrypt
+            local = "receiver"
+            inp = "encrypted"
+            outp = "input"
+
+        for data in self.legacy_data:
+            logmsg("%s: %s" % (mode, data["test"]))
+            p = data["params"][mode]
+
+            if "pad" in p and mode == "encrypt":
+                # This library doesn't pad in exactly the same way.
+                continue
+
+            if "keys" in data:
+                key = None
+                decode_pub = ec.EllipticCurvePublicNumbers.from_encoded_point
+                pubnum = decode_pub(ec.SECP256R1(), 
b64d(data["keys"][local]["public"]))
+                d = 0
+                dbin = b64d(data["keys"][local]["private"])
+                for i in range(0, len(dbin), 4):
+                    d = (d << 32) + struct.unpack("!L", dbin[i : i + 4])[0]
+                privnum = ec.EllipticCurvePrivateNumbers(d, pubnum)
+                private_key = privnum.private_key(default_backend())
+            else:
+                key = b64d(p["key"])
+                private_key = None
+
+            if "authSecret" in p:
+                auth_secret = b64d(p["authSecret"])
+            else:
+                auth_secret = None
+            if "dh" in p:
+                dh = b64d(p["dh"])
+            else:
+                dh = None
+
+            result = func(
+                b64d(data[inp]),
+                salt=b64d(p["salt"]),
+                key=key,
+                dh=dh,
+                auth_secret=auth_secret,
+                keyid=p.get("keyid"),
+                private_key=private_key,
+                rs=p.get("rs", 4096),
+                version=p["version"],
+            )
+            assert b64d(data[outp]) == result
+
+    def test_decrypt(self):
+        self._run("decrypt")
+
+    def test_encrypt(self):
+        self._run("encrypt")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/http_ece-1.1.0/http_ece.egg-info/PKG-INFO 
new/http_ece-1.2.1/http_ece.egg-info/PKG-INFO
--- old/http_ece-1.1.0/http_ece.egg-info/PKG-INFO       2019-02-11 
22:37:49.000000000 +0100
+++ new/http_ece-1.2.1/http_ece.egg-info/PKG-INFO       2024-08-08 
02:10:46.000000000 +0200
@@ -1,17 +1,24 @@
-Metadata-Version: 1.1
-Name: http-ece
-Version: 1.1.0
+Metadata-Version: 2.1
+Name: http_ece
+Version: 1.2.1
 Summary: Encrypted Content Encoding for HTTP
 Home-page: https://github.com/martinthomson/encrypted-content-encoding
 Author: Martin Thomson
 Author-email: [email protected]
 License: MIT
-Description: Encipher HTTP Messages
 Keywords: crypto http
-Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Requires-Dist: cryptography>=2.5
+
+Encipher HTTP Messages
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/http_ece-1.1.0/http_ece.egg-info/SOURCES.txt 
new/http_ece-1.2.1/http_ece.egg-info/SOURCES.txt
--- old/http_ece-1.1.0/http_ece.egg-info/SOURCES.txt    2019-02-11 
22:37:49.000000000 +0100
+++ new/http_ece-1.2.1/http_ece.egg-info/SOURCES.txt    2024-08-08 
02:10:46.000000000 +0200
@@ -2,9 +2,11 @@
 README.rst
 setup.cfg
 setup.py
+tox.ini
 http_ece/__init__.py
 http_ece.egg-info/PKG-INFO
 http_ece.egg-info/SOURCES.txt
 http_ece.egg-info/dependency_links.txt
 http_ece.egg-info/requires.txt
-http_ece.egg-info/top_level.txt
\ No newline at end of file
+http_ece.egg-info/top_level.txt
+http_ece/tests/test_ece.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/http_ece-1.1.0/setup.cfg new/http_ece-1.2.1/setup.cfg
--- old/http_ece-1.1.0/setup.cfg        2019-02-11 22:37:49.000000000 +0100
+++ new/http_ece-1.2.1/setup.cfg        2024-08-08 02:10:46.566744800 +0200
@@ -1,9 +1,6 @@
 [bdist_wheel]
 universal = 1
 
-[nosetests]
-config = .noserc
-
 [egg_info]
 tag_build = 
 tag_date = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/http_ece-1.1.0/setup.py new/http_ece-1.2.1/setup.py
--- old/http_ece-1.1.0/setup.py 2019-02-11 22:30:33.000000000 +0100
+++ new/http_ece-1.2.1/setup.py 2024-08-08 02:06:24.000000000 +0200
@@ -5,37 +5,40 @@
 from setuptools import setup
 
 here = os.path.abspath(os.path.dirname(__file__))
-with io.open(os.path.join(here, 'README.rst'), encoding='utf8') as f:
-        README = f.read()
+with io.open(os.path.join(here, "README.rst"), encoding="utf8") as f:
+    README = f.read()
 
 setup(
-    name='http_ece',
-    version='1.1.0',
-    author='Martin Thomson',
-    author_email='[email protected]',
+    name="http_ece",
+    version="1.2.1",
+    author="Martin Thomson",
+    author_email="[email protected]",
     scripts=[],
-    packages=['http_ece'],
-    description='Encrypted Content Encoding for HTTP',
-    long_description='Encipher HTTP Messages',
+    packages=["http_ece"],
+    description="Encrypted Content Encoding for HTTP",
+    long_description="Encipher HTTP Messages",
     classifiers=[
-        'Development Status :: 4 - Beta',
-        'License :: OSI Approved :: MIT License',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3.4',
-        'Programming Language :: Python :: 3.5',
-        'Programming Language :: Python :: 3.6',
+        "Development Status :: 4 - Beta",
+        "License :: OSI Approved :: MIT License",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3.4",
+        "Programming Language :: Python :: 3.5",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
+        "Programming Language :: Python :: 3.12",
     ],
-    keywords='crypto http',
+    keywords="crypto http",
     install_requires=[
-        'cryptography>=2.5',
+        "cryptography>=2.5",
     ],
     tests_require=[
-        'nose',
-        'mock',
-        'coverage',
-        'flake8',
+        "pytest",
+        "pytest-cov",
     ],
-    test_suite="nose.collector",
-    url='https://github.com/martinthomson/encrypted-content-encoding',
-    license='MIT'
+    url="https://github.com/martinthomson/encrypted-content-encoding";,
+    license="MIT",
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/http_ece-1.1.0/tox.ini new/http_ece-1.2.1/tox.ini
--- old/http_ece-1.1.0/tox.ini  1970-01-01 01:00:00.000000000 +0100
+++ new/http_ece-1.2.1/tox.ini  2024-08-08 02:06:24.000000000 +0200
@@ -0,0 +1,25 @@
+# Tox (http://tox.testrun.org/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py27,py34,py35,py38,py39,py310,py311,py312
+
+[testenv]
+basepython =
+  py27: python2.7
+  py34: python3.4
+  py35: python3.5
+  py38: python3.8
+  py39: python3.9
+  py310: python3.10
+  py311: python3.11
+  py312: python3.12
+commands =
+  pytest \
+    []
+deps =
+  pytest
+  pytest-cov
+  flake8

Reply via email to