From 26203d77d2f4c177e91a5beac07d8db2d412958c Mon Sep 17 00:00:00 2001 From: Robert de Vries Date: Sat, 11 Apr 2026 18:16:19 +0200 Subject: [PATCH 1/2] Fix mutable arguments passed as default arguments. Function defaults are evaluated once, when the function is defined. The same mutable object is then shared across all calls to the function. If the object is modified, those modifications will persist across calls, which can lead to unexpected behavior. --- tests/test_ciphers.py | 6 ++++-- wolfcrypt/ciphers.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_ciphers.py b/tests/test_ciphers.py index 4647109..b1ae94a 100644 --- a/tests/test_ciphers.py +++ b/tests/test_ciphers.py @@ -25,6 +25,7 @@ import pytest from wolfcrypt._ffi import lib as _lib from wolfcrypt.ciphers import MODE_CTR, MODE_ECB, MODE_CBC, WolfCryptError +from wolfcrypt.random import Random from wolfcrypt.utils import t2b, h2b import os @@ -613,11 +614,12 @@ def test_ecc_sign_verify_raw(ecc_private, ecc_public): def test_ecc_make_shared_secret(): - a = EccPrivate.make_key(32) + rng = Random() + a = EccPrivate.make_key(32, rng=rng) a_pub = EccPublic() a_pub.import_x963(a.export_x963()) - b = EccPrivate.make_key(32) + b = EccPrivate.make_key(32, rng=rng) b_pub = EccPublic() b_pub.import_x963(b.export_x963()) diff --git a/wolfcrypt/ciphers.py b/wolfcrypt/ciphers.py index 77ea987..cc5857c 100644 --- a/wolfcrypt/ciphers.py +++ b/wolfcrypt/ciphers.py @@ -2450,6 +2450,8 @@ def sign_with_seed(self, message, seed, ctx=None): :return: signature :rtype: bytes """ + if rng is None: + rng = Random() msg_bytestype = t2b(message) in_size = self.sig_size signature = _ffi.new(f"byte[{in_size}]") From b4ddce17deb970ebfe9339d5e8fe35835ac0c8dc Mon Sep 17 00:00:00 2001 From: Robert de Vries Date: Wed, 15 Apr 2026 16:58:02 +0200 Subject: [PATCH 2/2] Keep reference to random number generator in EccPrivate. Add a test for the case where no random number generator is passed to EccPrivate. --- tests/test_ciphers.py | 12 +++++++++--- wolfcrypt/ciphers.py | 23 ++++++++++------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/tests/test_ciphers.py b/tests/test_ciphers.py index b1ae94a..c84a7cc 100644 --- a/tests/test_ciphers.py +++ b/tests/test_ciphers.py @@ -614,12 +614,11 @@ def test_ecc_sign_verify_raw(ecc_private, ecc_public): def test_ecc_make_shared_secret(): - rng = Random() - a = EccPrivate.make_key(32, rng=rng) + a = EccPrivate.make_key(32, rng=Random()) a_pub = EccPublic() a_pub.import_x963(a.export_x963()) - b = EccPrivate.make_key(32, rng=rng) + b = EccPrivate.make_key(32, rng=Random()) b_pub = EccPublic() b_pub.import_x963(b.export_x963()) @@ -628,6 +627,13 @@ def test_ecc_make_shared_secret(): == a.shared_secret(b_pub) \ == b.shared_secret(a_pub) + def test_ecc_make_key_no_rng(): + key = EccPrivate.make_key(32) + pub_key = EccPublic() + pub_key.import_x963(key.export_x963()) + + assert key.shared_secret(pub_key) + if _lib.ED25519_ENABLED: @pytest.fixture def ed25519_private(vectors): diff --git a/wolfcrypt/ciphers.py b/wolfcrypt/ciphers.py index cc5857c..060475a 100644 --- a/wolfcrypt/ciphers.py +++ b/wolfcrypt/ciphers.py @@ -1229,31 +1229,30 @@ def verify_raw(self, R, S, data): class EccPrivate(EccPublic): + + def __init__(self, key=None, rng=None): + super().__init__(key) + if rng is None: + rng = Random() + self._rng = rng + @classmethod def make_key(cls, size, rng=None): """ Generates a new key pair of desired length **size**. """ - if rng is None: - rng = Random() - ecc = cls() - - ret = _lib.wc_ecc_make_key(rng.native_object, size, + ecc = cls(rng=rng) + ret = _lib.wc_ecc_make_key(ecc._rng.native_object, size, ecc.native_object) if ret < 0: raise WolfCryptError("Key generation error (%d)" % ret) if _lib.ECC_TIMING_RESISTANCE_ENABLED and (not _lib.FIPS_ENABLED or _lib.FIPS_VERSION > 2): - ret = _lib.wc_ecc_set_rng(ecc.native_object, rng.native_object) + ret = _lib.wc_ecc_set_rng(ecc.native_object, ecc._rng.native_object) if ret < 0: raise WolfCryptError("Error setting ECC RNG (%d)" % ret) - # Retain the RNG so it outlives the ECC key. Even outside the - # timing-resistance path, wolfSSL internals may retain a pointer - # to the RNG; keeping the reference avoids any UAF risk. - ecc._rng = rng - return ecc def decode_key(self, key): @@ -2450,8 +2449,6 @@ def sign_with_seed(self, message, seed, ctx=None): :return: signature :rtype: bytes """ - if rng is None: - rng = Random() msg_bytestype = t2b(message) in_size = self.sig_size signature = _ffi.new(f"byte[{in_size}]")