Skip to content

Commit 018bafe

Browse files
committed
Fix hash copy semantics and add tests
1 parent 17f3332 commit 018bafe

6 files changed

Lines changed: 169 additions & 39 deletions

File tree

scripts/build_ffi.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ def build_ffi(local_wolfssl, features):
575575
int wc_ShaUpdate(wc_Sha*, const byte*, word32);
576576
int wc_ShaFinal(wc_Sha*, byte*);
577577
void wc_ShaFree(wc_Sha*);
578+
int wc_ShaCopy(wc_Sha*, wc_Sha*);
578579
"""
579580

580581
if features["SHA256"]:
@@ -584,6 +585,7 @@ def build_ffi(local_wolfssl, features):
584585
int wc_Sha256Update(wc_Sha256*, const byte*, word32);
585586
int wc_Sha256Final(wc_Sha256*, byte*);
586587
void wc_Sha256Free(wc_Sha256*);
588+
int wc_Sha256Copy(wc_Sha256*, wc_Sha256*);
587589
"""
588590

589591
if features["SHA384"]:
@@ -593,6 +595,7 @@ def build_ffi(local_wolfssl, features):
593595
int wc_Sha384Update(wc_Sha384*, const byte*, word32);
594596
int wc_Sha384Final(wc_Sha384*, byte*);
595597
void wc_Sha384Free(wc_Sha384*);
598+
int wc_Sha384Copy(wc_Sha384*, wc_Sha384*);
596599
"""
597600

598601
if features["SHA512"]:
@@ -603,6 +606,7 @@ def build_ffi(local_wolfssl, features):
603606
int wc_Sha512Update(wc_Sha512*, const byte*, word32);
604607
int wc_Sha512Final(wc_Sha512*, byte*);
605608
void wc_Sha512Free(wc_Sha512*);
609+
int wc_Sha512Copy(wc_Sha512*, wc_Sha512*);
606610
"""
607611
if features["SHA3"]:
608612
cdef += """
@@ -623,6 +627,10 @@ def build_ffi(local_wolfssl, features):
623627
void wc_Sha3_256_Free(wc_Sha3*);
624628
void wc_Sha3_384_Free(wc_Sha3*);
625629
void wc_Sha3_512_Free(wc_Sha3*);
630+
int wc_Sha3_224_Copy(wc_Sha3*, wc_Sha3*);
631+
int wc_Sha3_256_Copy(wc_Sha3*, wc_Sha3*);
632+
int wc_Sha3_384_Copy(wc_Sha3*, wc_Sha3*);
633+
int wc_Sha3_512_Copy(wc_Sha3*, wc_Sha3*);
626634
"""
627635

628636
if features["DES3"]:

tests/test_aesgcmstream.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,27 @@ def test_encrypt_aad_bad():
126126
def test_invalid_tag_bytes():
127127
key = "fedcba9876543210"
128128
iv = "0123456789abcdef"
129-
with pytest.raises(ValueError, match="tag_bytes must be between 4 and 16"):
129+
# Out of range
130+
with pytest.raises(ValueError, match="tag_bytes must be one of"):
130131
AesGcmStream(key, iv, tag_bytes=0)
131-
with pytest.raises(ValueError, match="tag_bytes must be between 4 and 16"):
132+
with pytest.raises(ValueError, match="tag_bytes must be one of"):
132133
AesGcmStream(key, iv, tag_bytes=3)
133-
with pytest.raises(ValueError, match="tag_bytes must be between 4 and 16"):
134+
with pytest.raises(ValueError, match="tag_bytes must be one of"):
134135
AesGcmStream(key, iv, tag_bytes=17)
135-
# valid edge cases
136-
AesGcmStream(key, iv, tag_bytes=4)
137-
AesGcmStream(key, iv, tag_bytes=16)
136+
# Non-NIST sizes within 4-16 range
137+
for bad in (5, 6, 7, 9, 10, 11):
138+
with pytest.raises(ValueError, match="tag_bytes must be one of"):
139+
AesGcmStream(key, iv, tag_bytes=bad)
140+
# Valid NIST sizes
141+
for good in (4, 8, 12, 13, 14, 15, 16):
142+
AesGcmStream(key, iv, tag_bytes=good)
143+
144+
def test_repeated_construction_destruction():
145+
import gc
146+
key = "fedcba9876543210"
147+
iv = "0123456789abcdef"
148+
for _ in range(1000):
149+
gcm = AesGcmStream(key, iv)
150+
gcm.encrypt("hello world")
151+
del gcm
152+
gc.collect()

tests/test_ciphers.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,3 +898,15 @@ def test_chacha_non_block_aligned():
898898
def test_chacha_invalid_key_length():
899899
with pytest.raises(ValueError, match="key must be"):
900900
ChaCha(b"\x00" * 20)
901+
902+
903+
if _lib.RSA_ENABLED:
904+
def test_encrypt_oaep_requires_hash_type(vectors):
905+
rsa = RsaPublic(vectors[RsaPublic].key)
906+
with pytest.raises(WolfCryptError, match="Hash type not set"):
907+
rsa.encrypt_oaep(b"plaintext")
908+
909+
def test_decrypt_oaep_requires_hash_type(vectors):
910+
rsa = RsaPrivate(vectors[RsaPrivate].key)
911+
with pytest.raises(WolfCryptError, match="Hash type not set"):
912+
rsa.decrypt_oaep(b"\x00" * rsa.output_size)

tests/test_hashes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,12 @@ def test_hash(hash_cls, vectors):
184184
copy.update("wolfcrypt")
185185

186186
assert hash_obj.hexdigest() == copy.hexdigest() == digest
187+
188+
189+
def test_hash_repeated_construction_destruction(hash_cls):
190+
import gc
191+
for _ in range(1000):
192+
h = hash_new(hash_cls, "wolfcrypt")
193+
h.hexdigest()
194+
del h
195+
gc.collect()

wolfcrypt/ciphers.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -396,32 +396,36 @@ 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
402399

403400
def __init__(self, key, IV, tag_bytes=16):
404401
"""
405402
tag_bytes is the number of bytes to use for the authentication tag during encryption
406403
"""
407404
key = t2b(key)
408405
IV = t2b(IV)
409-
if tag_bytes < 4 or tag_bytes > 16:
410-
raise ValueError("tag_bytes must be between 4 and 16")
406+
# NIST SP 800-38D valid GCM tag lengths: 16, 15, 14, 13, 12, 8, 4 bytes.
407+
if tag_bytes not in (4, 8, 12, 13, 14, 15, 16):
408+
raise ValueError(
409+
"tag_bytes must be one of 4, 8, 12, 13, 14, 15, or 16")
410+
# Per-instance state: AAD, tag length, and current mode (enc/dec).
411+
self._aad = bytes()
411412
self._tag_bytes = tag_bytes
413+
self._mode = None
412414
if len(key) not in self._key_sizes:
413415
raise ValueError("key must be %s in length, not %d" %
414416
(self._key_sizes, len(key)))
417+
self._init_done = False
415418
self._native_object = _ffi.new(self._native_type)
416419
ret = _lib.wc_AesInit(self._native_object, _ffi.NULL, -2)
417420
if ret < 0:
418421
raise WolfCryptError("AES init error (%d)" % ret)
422+
self._init_done = True
419423
ret = _lib.wc_AesGcmInit(self._native_object, key, len(key), IV, len(IV))
420424
if ret < 0:
421425
raise WolfCryptError("Init error (%d)" % ret)
422426

423427
def __del__(self):
424-
if hasattr(self, '_native_object'):
428+
if getattr(self, '_init_done', False):
425429
_lib.wc_AesFree(self._native_object)
426430

427431
def set_aad(self, data):
@@ -446,11 +450,11 @@ def encrypt(self, data):
446450
aad = self._aad
447451
elif self._mode == _DECRYPTION:
448452
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))
453+
buf = _ffi.new("byte[%d]" % (len(data)))
454+
ret = _lib.wc_AesGcmEncryptUpdate(self._native_object, buf, data, len(data), aad, len(aad))
451455
if ret < 0:
452456
raise WolfCryptError("Encryption error (%d)" % ret)
453-
return bytes(self._buf)
457+
return bytes(buf)
454458

455459
def decrypt(self, data):
456460
"""
@@ -463,11 +467,11 @@ def decrypt(self, data):
463467
aad = self._aad
464468
elif self._mode == _ENCRYPTION:
465469
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))
470+
buf = _ffi.new("byte[%d]" % (len(data)))
471+
ret = _lib.wc_AesGcmDecryptUpdate(self._native_object, buf, data, len(data), aad, len(aad))
468472
if ret < 0:
469473
raise WolfCryptError("Decryption error (%d)" % ret)
470-
return bytes(self._buf)
474+
return bytes(buf)
471475

472476
def final(self, authTag=None):
473477
"""
@@ -505,7 +509,9 @@ class ChaCha(_Cipher):
505509
_IV_nonce = b""
506510
_IV_counter = 0
507511

508-
def __init__(self, key="", size=32):
512+
def __init__(self, key="", size=32): # pylint: disable=unused-argument
513+
# size is kept for backwards compatibility; key length is now
514+
# derived from the actual key and validated against _key_sizes.
509515
self._native_object = _ffi.new(self._native_type)
510516
self._enc = None
511517
self._dec = None
@@ -1231,7 +1237,11 @@ def make_key(cls, size, rng=None):
12311237
ret = _lib.wc_ecc_set_rng(ecc.native_object, rng.native_object)
12321238
if ret < 0:
12331239
raise WolfCryptError("Error setting ECC RNG (%d)" % ret)
1234-
ecc._rng = rng
1240+
1241+
# Retain the RNG so it outlives the ECC key. Even outside the
1242+
# timing-resistance path, wolfSSL internals may retain a pointer
1243+
# to the RNG; keeping the reference avoids any UAF risk.
1244+
ecc._rng = rng
12351245

12361246
return ecc
12371247

0 commit comments

Comments
 (0)