@@ -396,33 +396,40 @@ class AesGcmStream(object):
396396 block_size = 16
397397 _key_sizes = [16 , 24 , 32 ]
398398 _native_type = "Aes *"
399- _aad = bytes ()
400- _tag_bytes = 16
401- _mode = None
399+ # making sure _lib.wc_AesFree outlives Aes instances
400+ _delete = _lib .wc_AesFree
402401
403402 def __init__ (self , key , IV , tag_bytes = 16 ):
404403 """
405404 tag_bytes is the number of bytes to use for the authentication tag during encryption
406405 """
407406 key = t2b (key )
408407 IV = t2b (IV )
409- if tag_bytes < 4 or tag_bytes > 16 :
410- raise ValueError ("tag_bytes must be between 4 and 16" )
408+ # NIST SP 800-38D valid GCM tag lengths: 16, 15, 14, 13, 12, 8, 4 bytes.
409+ if tag_bytes not in (4 , 8 , 12 , 13 , 14 , 15 , 16 ):
410+ raise ValueError (
411+ "tag_bytes must be one of 4, 8, 12, 13, 14, 15, or 16" )
412+ # Per-instance state: AAD, tag length, and current mode (enc/dec).
413+ self ._aad = bytes ()
411414 self ._tag_bytes = tag_bytes
415+ self ._mode = None
412416 if len (key ) not in self ._key_sizes :
413417 raise ValueError ("key must be %s in length, not %d" %
414418 (self ._key_sizes , len (key )))
419+ self ._init_done = False
415420 self ._native_object = _ffi .new (self ._native_type )
416421 ret = _lib .wc_AesInit (self ._native_object , _ffi .NULL , - 2 )
417422 if ret < 0 :
418423 raise WolfCryptError ("AES init error (%d)" % ret )
424+ self ._init_done = True
419425 ret = _lib .wc_AesGcmInit (self ._native_object , key , len (key ), IV , len (IV ))
420426 if ret < 0 :
421427 raise WolfCryptError ("Init error (%d)" % ret )
422428
423429 def __del__ (self ):
424- if hasattr (self , '_native_object' ):
425- _lib .wc_AesFree (self ._native_object )
430+ if getattr (self , '_init_done' , False ):
431+ self ._delete (self ._native_object )
432+ self ._init_done = False
426433
427434 def set_aad (self , data ):
428435 """
@@ -446,11 +453,11 @@ def encrypt(self, data):
446453 aad = self ._aad
447454 elif self ._mode == _DECRYPTION :
448455 raise WolfCryptError ("Class instance already in use for decryption" )
449- self . _buf = _ffi .new ("byte[%d]" % (len (data )))
450- ret = _lib .wc_AesGcmEncryptUpdate (self ._native_object , self . _buf , data , len (data ), aad , len (aad ))
456+ buf = _ffi .new ("byte[%d]" % (len (data )))
457+ ret = _lib .wc_AesGcmEncryptUpdate (self ._native_object , buf , data , len (data ), aad , len (aad ))
451458 if ret < 0 :
452459 raise WolfCryptError ("Encryption error (%d)" % ret )
453- return bytes (self . _buf )
460+ return bytes (buf )
454461
455462 def decrypt (self , data ):
456463 """
@@ -463,11 +470,11 @@ def decrypt(self, data):
463470 aad = self ._aad
464471 elif self ._mode == _ENCRYPTION :
465472 raise WolfCryptError ("Class instance already in use for encryption" )
466- self . _buf = _ffi .new ("byte[%d]" % (len (data )))
467- ret = _lib .wc_AesGcmDecryptUpdate (self ._native_object , self . _buf , data , len (data ), aad , len (aad ))
473+ buf = _ffi .new ("byte[%d]" % (len (data )))
474+ ret = _lib .wc_AesGcmDecryptUpdate (self ._native_object , buf , data , len (data ), aad , len (aad ))
468475 if ret < 0 :
469476 raise WolfCryptError ("Decryption error (%d)" % ret )
470- return bytes (self . _buf )
477+ return bytes (buf )
471478
472479 def final (self , authTag = None ):
473480 """
@@ -505,7 +512,9 @@ class ChaCha(_Cipher):
505512 _IV_nonce = b""
506513 _IV_counter = 0
507514
508- def __init__ (self , key = "" , size = 32 ):
515+ def __init__ (self , key = "" , size = 32 ): # pylint: disable=unused-argument
516+ # size is kept for backwards compatibility; key length is now
517+ # derived from the actual key and validated against _key_sizes.
509518 self ._native_object = _ffi .new (self ._native_type )
510519 self ._enc = None
511520 self ._dec = None
@@ -552,7 +561,9 @@ def set_iv(self, nonce, counter = 0):
552561 raise ValueError ("nonce must be %d bytes, got %d" %
553562 (self ._NONCE_SIZE , len (self ._IV_nonce )))
554563 self ._IV_counter = counter
555- self ._set_key (0 )
564+ ret = self ._set_key (0 )
565+ if ret < 0 :
566+ raise WolfCryptError ("ChaCha set_iv error (%d)" % ret )
556567
557568if _lib .CHACHA20_POLY1305_ENABLED :
558569 class ChaCha20Poly1305 (object ):
@@ -643,6 +654,9 @@ class Des3(_Cipher):
643654 _native_type = "Des3 *"
644655
645656 def __init__ (self , key , mode , IV = None ):
657+ # Intentionally stricter than _Cipher.__init__, which accepts both
658+ # CBC and CTR. wolfCrypt has no 3DES-CTR implementation, so reject
659+ # MODE_CTR here with a clearer error before delegating.
646660 if mode != MODE_CBC :
647661 raise ValueError ("Des3 only supports MODE_CBC" )
648662 super ().__init__ (key , mode , IV )
@@ -864,6 +878,9 @@ def make_key(cls, size, rng=None, hash_type=None):
864878 if rsa .output_size <= 0 : # pragma: no cover
865879 raise WolfCryptError ("Invalid key size error (%d)" % ret )
866880
881+ # Retain RNG reference defensively.
882+ rsa ._rng = rng
883+
867884 return rsa
868885
869886 def __init__ (self , key = None , hash_type = None ): # pylint: disable=super-init-not-called
@@ -1231,7 +1248,11 @@ def make_key(cls, size, rng=None):
12311248 ret = _lib .wc_ecc_set_rng (ecc .native_object , rng .native_object )
12321249 if ret < 0 :
12331250 raise WolfCryptError ("Error setting ECC RNG (%d)" % ret )
1234- ecc ._rng = rng
1251+
1252+ # Retain the RNG so it outlives the ECC key. Even outside the
1253+ # timing-resistance path, wolfSSL internals may retain a pointer
1254+ # to the RNG; keeping the reference avoids any UAF risk.
1255+ ecc ._rng = rng
12351256
12361257 return ecc
12371258
@@ -1504,6 +1525,10 @@ def make_key(cls, size, rng=None):
15041525 if ret < 0 :
15051526 raise WolfCryptError ("Key generation error (%d)" % ret )
15061527
1528+ # Retain RNG reference defensively; wolfSSL may retain a pointer
1529+ # internally on some builds.
1530+ ed25519 ._rng = rng
1531+
15071532 return ed25519
15081533
15091534 def decode_key (self , key , pub = None ):
@@ -1706,6 +1731,10 @@ def make_key(cls, size, rng=None):
17061731 if ret < 0 :
17071732 raise WolfCryptError ("Key generation error (%d)" % ret )
17081733
1734+ # Retain RNG reference defensively; wolfSSL may retain a pointer
1735+ # internally on some builds.
1736+ ed448 ._rng = rng
1737+
17091738 return ed448
17101739
17111740 def decode_key (self , key , pub = None ):
@@ -1979,6 +2008,9 @@ def make_key(cls, mlkem_type, rng=None):
19792008 if ret < 0 : # pragma: no cover
19802009 raise WolfCryptError ("wc_KyberKey_MakeKey() error (%d)" % ret )
19812010
2011+ # Retain RNG reference defensively.
2012+ mlkem_priv ._rng = rng
2013+
19822014 return mlkem_priv
19832015
19842016 @classmethod
@@ -2226,6 +2258,9 @@ def make_key(cls, mldsa_type, rng=None):
22262258 if ret < 0 : # pragma: no cover
22272259 raise WolfCryptError ("wc_dilithium_make_key() error (%d)" % ret )
22282260
2261+ # Retain RNG reference defensively.
2262+ mldsa_priv ._rng = rng
2263+
22292264 return mldsa_priv
22302265
22312266 @property
0 commit comments