Skip to content

Commit 2565f85

Browse files
committed
Fix hash copy semantics and add tests
1 parent 17f3332 commit 2565f85

5 files changed

Lines changed: 97 additions & 20 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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,13 @@ def test_invalid_tag_bytes():
135135
# valid edge cases
136136
AesGcmStream(key, iv, tag_bytes=4)
137137
AesGcmStream(key, iv, tag_bytes=16)
138+
139+
def test_repeated_construction_destruction():
140+
import gc
141+
key = "fedcba9876543210"
142+
iv = "0123456789abcdef"
143+
for _ in range(1000):
144+
gcm = AesGcmStream(key, iv)
145+
gcm.encrypt("hello world")
146+
del gcm
147+
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)

wolfcrypt/ciphers.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,6 @@ 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
"""
@@ -408,7 +405,9 @@ def __init__(self, key, IV, tag_bytes=16):
408405
IV = t2b(IV)
409406
if tag_bytes < 4 or tag_bytes > 16:
410407
raise ValueError("tag_bytes must be between 4 and 16")
408+
self._aad = bytes()
411409
self._tag_bytes = tag_bytes
410+
self._mode = None
412411
if len(key) not in self._key_sizes:
413412
raise ValueError("key must be %s in length, not %d" %
414413
(self._key_sizes, len(key)))
@@ -505,7 +504,9 @@ class ChaCha(_Cipher):
505504
_IV_nonce = b""
506505
_IV_counter = 0
507506

508-
def __init__(self, key="", size=32):
507+
def __init__(self, key="", size=32): # pylint: disable=unused-argument
508+
# size is kept for backwards compatibility; key length is now
509+
# derived from the actual key and validated against _key_sizes.
509510
self._native_object = _ffi.new(self._native_type)
510511
self._enc = None
511512
self._dec = None

wolfcrypt/hashes.py

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,16 @@ def copy(self):
5858
"""
5959
copy = self.new("")
6060

61-
_ffi.memmove(copy._native_object, # pylint: disable=protected-access
62-
self._native_object,
63-
self._native_size)
61+
if hasattr(self, '_copy'):
62+
ret = self._copy(self._native_object,
63+
copy._native_object) # pylint: disable=protected-access
64+
if ret < 0: # pragma: no cover
65+
raise WolfCryptError("Hash copy error (%d)" % ret)
66+
else:
67+
_ffi.memmove(copy._native_object, # pylint: disable=protected-access
68+
self._native_object,
69+
self._native_size)
70+
copy._shallow_copy = True # pylint: disable=protected-access
6471

6572
return copy
6673

@@ -87,12 +94,26 @@ def digest(self):
8794

8895
if self._native_object:
8996
obj = _ffi.new(self._native_type)
90-
91-
_ffi.memmove(obj, self._native_object, self._native_size)
92-
93-
ret = self._final(obj, result)
94-
if ret < 0: # pragma: no cover
95-
raise WolfCryptError("Hash finalize error (%d)" % ret)
97+
used_deep_copy = hasattr(self, '_copy')
98+
99+
if used_deep_copy:
100+
ret = self._copy(self._native_object, obj)
101+
if ret < 0: # pragma: no cover
102+
raise WolfCryptError("Hash copy error (%d)" % ret)
103+
else:
104+
_ffi.memmove(obj, self._native_object, self._native_size)
105+
106+
try:
107+
ret = self._final(obj, result)
108+
if ret < 0: # pragma: no cover
109+
raise WolfCryptError("Hash finalize error (%d)" % ret)
110+
finally:
111+
# Only free when we did a deep copy; memmove'd temps share
112+
# internal resources with self and must not be separately freed.
113+
if used_deep_copy:
114+
delete = getattr(self, '_delete', None)
115+
if delete:
116+
delete(obj)
96117

97118
return _ffi.buffer(result, self.digest_size)[:]
98119

@@ -117,9 +138,11 @@ class Sha(_Hash):
117138
_native_type = "wc_Sha *"
118139
_native_size = _ffi.sizeof("wc_Sha")
119140
_delete = _lib.wc_ShaFree
141+
_copy = _lib.wc_ShaCopy
120142

121143
def __del__(self):
122-
self._delete(self._native_object)
144+
if hasattr(self, '_native_object') and not getattr(self, '_shallow_copy', False):
145+
self._delete(self._native_object)
123146

124147
def _init(self):
125148
return _lib.wc_InitSha(self._native_object)
@@ -143,9 +166,11 @@ class Sha256(_Hash):
143166
_native_type = "wc_Sha256 *"
144167
_native_size = _ffi.sizeof("wc_Sha256")
145168
_delete = _lib.wc_Sha256Free
169+
_copy = _lib.wc_Sha256Copy
146170

147171
def __del__(self):
148-
self._delete(self._native_object)
172+
if hasattr(self, '_native_object') and not getattr(self, '_shallow_copy', False):
173+
self._delete(self._native_object)
149174

150175
def _init(self):
151176
return _lib.wc_InitSha256(self._native_object)
@@ -169,9 +194,11 @@ class Sha384(_Hash):
169194
_native_type = "wc_Sha384 *"
170195
_native_size = _ffi.sizeof("wc_Sha384")
171196
_delete = _lib.wc_Sha384Free
197+
_copy = _lib.wc_Sha384Copy
172198

173199
def __del__(self):
174-
self._delete(self._native_object)
200+
if hasattr(self, '_native_object') and not getattr(self, '_shallow_copy', False):
201+
self._delete(self._native_object)
175202

176203
def _init(self):
177204
return _lib.wc_InitSha384(self._native_object)
@@ -195,9 +222,11 @@ class Sha512(_Hash):
195222
_native_type = "wc_Sha512 *"
196223
_native_size = _ffi.sizeof("wc_Sha512")
197224
_delete = _lib.wc_Sha512Free
225+
_copy = _lib.wc_Sha512Copy
198226

199227
def __del__(self):
200-
self._delete(self._native_object)
228+
if hasattr(self, '_native_object') and not getattr(self, '_shallow_copy', False):
229+
self._delete(self._native_object)
201230

202231
def _init(self):
203232
return _lib.wc_InitSha512(self._native_object)
@@ -232,14 +261,24 @@ class Sha3(_Hash):
232261
64: _lib.wc_Sha3_512_Free,
233262
}
234263

264+
_SHA3_COPY = {
265+
28: _lib.wc_Sha3_224_Copy,
266+
32: _lib.wc_Sha3_256_Copy,
267+
48: _lib.wc_Sha3_384_Copy,
268+
64: _lib.wc_Sha3_512_Copy,
269+
}
270+
235271
def __del__(self):
236-
if hasattr(self, '_delete'):
272+
if (hasattr(self, '_native_object')
273+
and not getattr(self, '_shallow_copy', False)
274+
and getattr(self, '_delete', None)):
237275
self._delete(self._native_object)
238276

239277
def __init__(self, string=None, size=SHA3_384_DIGEST_SIZE): # pylint: disable=W0231
240278
self._native_object = _ffi.new(self._native_type)
241279
self.digest_size = size
242280
self._delete = self._SHA3_FREE.get(size)
281+
self._copy = self._SHA3_COPY.get(size)
243282
ret = self._init()
244283
if ret < 0: # pragma: no cover
245284
raise WolfCryptError("Sha3 init error (%d)" % ret)
@@ -252,7 +291,13 @@ def new(cls, string=None, size=SHA3_384_DIGEST_SIZE):
252291

253292
def copy(self):
254293
c = Sha3(size=self.digest_size)
255-
_ffi.memmove(c._native_object, self._native_object, self._native_size)
294+
if self._copy:
295+
ret = self._copy(self._native_object, c._native_object)
296+
if ret < 0: # pragma: no cover
297+
raise WolfCryptError("Hash copy error (%d)" % ret)
298+
else:
299+
_ffi.memmove(c._native_object, self._native_object, self._native_size)
300+
c._shallow_copy = True
256301
return c
257302

258303
def _init(self):
@@ -316,7 +361,8 @@ class _Hmac(_Hash):
316361
_delete = _lib.wc_HmacFree
317362

318363
def __del__(self):
319-
self._delete(self._native_object)
364+
if hasattr(self, '_native_object') and not getattr(self, '_shallow_copy', False):
365+
self._delete(self._native_object)
320366

321367
def __init__(self, key, string=None): # pylint: disable=W0231
322368
key = t2b(key)

0 commit comments

Comments
 (0)