diff --git a/pyproject.toml b/pyproject.toml index afb03ad..24606f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ ] dynamic = ["version"] dependencies = [ - "cffi>=1.0.0", + "cffi>=1.0.0,<2", ] [project.urls] diff --git a/requirements/docs.txt b/requirements/docs.txt index 651f417..3dfc429 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1,3 +1,3 @@ -r prod.txt -Sphinx -sphinx_rtd_theme +Sphinx>=7,<9 +sphinx_rtd_theme>=2,<4 diff --git a/requirements/prod.txt b/requirements/prod.txt index 6a88e4b..29aec0e 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -1 +1 @@ -cffi +cffi>=1.17,<2 diff --git a/requirements/test.txt b/requirements/test.txt index 53c4efe..015dd99 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,4 +1,4 @@ -r prod.txt -tox -pytest -types-cffi +tox>=4,<5 +pytest>=8,<9 +types-cffi>=1.16,<2 diff --git a/scripts/build_ffi.py b/scripts/build_ffi.py index 2a36eb2..ec170d5 100644 --- a/scripts/build_ffi.py +++ b/scripts/build_ffi.py @@ -545,8 +545,8 @@ def build_ffi(local_wolfssl, features): typedef struct { ...; } OS_Seed; int wc_InitRng(WC_RNG*); - int wc_InitRngNonce(WC_RNG*, byte*, word32); - int wc_InitRngNonce_ex(WC_RNG*, byte*, word32, void*, int); + int wc_InitRngNonce(WC_RNG*, const byte*, word32); + int wc_InitRngNonce_ex(WC_RNG*, const byte*, word32, void*, int); int wc_RNG_GenerateBlock(WC_RNG*, byte*, word32); int wc_RNG_GenerateByte(WC_RNG*, byte*); int wc_FreeRng(WC_RNG*); @@ -1039,10 +1039,12 @@ def build_ffi(local_wolfssl, features): RsaKey*, WC_RNG*); int wc_RsaPublicEncrypt_ex(const byte* in, word32 inLen, byte* out, word32 outLen, RsaKey* key, WC_RNG* rng, int type, - enum wc_HashType hash, int mgf, byte* label, word32 labelSz); + enum wc_HashType hash, int mgf, const byte* label, + word32 labelSz); int wc_RsaPrivateDecrypt_ex(const byte* in, word32 inLen, byte* out, word32 outLen, RsaKey* key, int type, - enum wc_HashType hash, int mgf, byte* label, word32 labelSz); + enum wc_HashType hash, int mgf, const byte* label, + word32 labelSz); int wc_RsaSSL_Sign(const byte*, word32, byte*, word32, RsaKey*, WC_RNG*); int wc_RsaSSL_Verify(const byte*, word32, byte*, word32, RsaKey*); """ diff --git a/setup.py b/setup.py index 290c19c..03ab474 100755 --- a/setup.py +++ b/setup.py @@ -60,8 +60,8 @@ packages=find_packages(), - setup_requires=["cffi>=1.0.0"], - install_requires=["cffi>=1.0.0"], + setup_requires=["cffi>=1.0.0,<2"], + install_requires=["cffi>=1.0.0,<2"], cffi_modules=["./scripts/build_ffi.py:ffibuilder"], package_data={"wolfcrypt": ["*.dll", "**/*.pyi"]} diff --git a/tests/test_aesgcmstream.py b/tests/test_aesgcmstream.py index 1d823f2..c22858b 100644 --- a/tests/test_aesgcmstream.py +++ b/tests/test_aesgcmstream.py @@ -50,7 +50,7 @@ def test_encrypt_short_tag(): authTag = gcm.final() assert b2h(authTag) == bytes('ac8fcee96dc6ef8e5236da19', 'utf-8') assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8") - gcmdec = AesGcmStream(key, iv) + gcmdec = AesGcmStream(key, iv, 12) bufdec = gcmdec.decrypt(buf) gcmdec.final(authTag) assert bufdec == t2b("hello world") @@ -144,6 +144,33 @@ def test_invalid_tag_bytes(): tag = gcm.final() assert len(tag) == good + def test_decrypt_rejects_wrong_tag_length(): + key = "fedcba9876543210" + iv = "0123456789abcdef" + gcm = AesGcmStream(key, iv, tag_bytes=16) + buf = gcm.encrypt("hello world") + authTag = gcm.final() + assert len(authTag) == 16 + + # Truncated tag: would silently lower the verification window to + # 32-bit forgery probability without this check. + gcmdec = AesGcmStream(key, iv, tag_bytes=16) + gcmdec.decrypt(buf) + with pytest.raises(ValueError, match="authTag must be 16 bytes"): + gcmdec.final(authTag[:4]) + + # Over-long tag is also rejected. + gcmdec2 = AesGcmStream(key, iv, tag_bytes=16) + gcmdec2.decrypt(buf) + with pytest.raises(ValueError, match="authTag must be 16 bytes"): + gcmdec2.final(authTag + b"\x00") + + # Happy path with the configured length still verifies. + gcmdec3 = AesGcmStream(key, iv, tag_bytes=16) + plain = gcmdec3.decrypt(buf) + gcmdec3.final(authTag) + assert plain == t2b("hello world") + def test_repeated_construction_destruction(): import gc key = "fedcba9876543210" diff --git a/wolfcrypt/ciphers.py b/wolfcrypt/ciphers.py index c613793..417b160 100644 --- a/wolfcrypt/ciphers.py +++ b/wolfcrypt/ciphers.py @@ -496,7 +496,12 @@ def final(self, authTag=None): if authTag is None: raise WolfCryptError("authTag parameter required") authTag = t2b(authTag) - ret = _lib.wc_AesGcmDecryptFinal(self._native_object, authTag, len(authTag)) + if len(authTag) != self._tag_bytes: + raise ValueError( + "authTag must be %d bytes, got %d" % + (self._tag_bytes, len(authTag))) + ret = _lib.wc_AesGcmDecryptFinal( + self._native_object, authTag, self._tag_bytes) if ret < 0: raise WolfCryptApiError("Decryption error", ret)