Skip to content

Commit 523b36b

Browse files
committed
Add support for multiple associated data blocks
1 parent 56e7814 commit 523b36b

File tree

3 files changed

+78
-25
lines changed

3 files changed

+78
-25
lines changed

scripts/build_ffi.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,10 @@ def build_ffi(local_wolfssl, features):
651651

652652
if features["AES"] and features["AES_SIV"]:
653653
cdef += """
654-
typedef struct { ...; } AesSivAssoc;
654+
typedef struct AesSivAssoc_s {
655+
const byte* assoc;
656+
word32 assocSz;
657+
} AesSivAssoc;
655658
int wc_AesSivEncrypt(const byte* key, word32 keySz, const byte* assoc,
656659
word32 assocSz, const byte* nonce, word32 nonceSz,
657660
const byte* in, word32 inSz, byte* siv, byte* out);

tests/test_ciphers.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161

6262
@pytest.fixture
6363
def vectors():
64-
TestVector = namedtuple("TestVector", """key iv plaintext ciphertext
64+
TestVector = namedtuple("TestVector", """key iv plaintext ciphertext
6565
ciphertext_ctr raw_key
6666
pkcs8_key pem""")
6767
TestVector.__new__.__defaults__ = (None,) * len(TestVector._fields)
@@ -778,20 +778,15 @@ def test_aessiv_encrypt_decrypt():
778778

779779

780780
@pytest.mark.skipif(not _lib.AES_SIV_ENABLED, reason="AES-SIV not enabled")
781-
@pytest.mark.skip(reason=("Associated data in test vector consists of "
782-
"multiple blocks which is unsupported"))
783781
def test_aessiv_encrypt_kat_rfc5297():
784782
"""
785783
Known-answer test using test vectors from RFC-5297.
786784
"""
787785
aessiv = AesSiv(TEST_VECTOR_KEY_RFC5297)
788-
# This is probably not the correct way of handling the associated data.
789-
# The function wc_AesSivEncrypt_ex supports this but it is currently not
790-
# exposed.
791-
associated_data = (
792-
TEST_VECTOR_ASSOCIATED_DATA_1_RFC5297
793-
+ TEST_VECTOR_ASSOCIATED_DATA_2_RFC5297
794-
)
786+
associated_data = [
787+
TEST_VECTOR_ASSOCIATED_DATA_1_RFC5297,
788+
TEST_VECTOR_ASSOCIATED_DATA_2_RFC5297,
789+
]
795790
siv, ciphertext = aessiv.encrypt(
796791
associated_data,
797792
TEST_VECTOR_NONCE_RFC5297,
@@ -802,19 +797,14 @@ def test_aessiv_encrypt_kat_rfc5297():
802797

803798

804799
@pytest.mark.skipif(not _lib.AES_SIV_ENABLED, reason="AES-SIV not enabled")
805-
@pytest.mark.skip(reason=("Associated data in test vector consists "
806-
"of multiple blocks which is unsupported"))
807800
def test_aessiv_decrypt_kat_rfc5297():
808801
"""
809802
Known-answer test using test vectors from RFC-5297.
810803
"""
811804
aessiv = AesSiv(TEST_VECTOR_KEY_RFC5297)
812-
# This is probably not the correct way of handling the associated data.
813-
# The function wc_AesSivEncrypt_ex supports this but it is currently not
814-
# exposed.
815805
associated_data = (
816-
TEST_VECTOR_ASSOCIATED_DATA_1_RFC5297
817-
+ TEST_VECTOR_ASSOCIATED_DATA_2_RFC5297
806+
TEST_VECTOR_ASSOCIATED_DATA_1_RFC5297,
807+
TEST_VECTOR_ASSOCIATED_DATA_2_RFC5297,
818808
)
819809
plaintext = aessiv.decrypt(
820810
associated_data,
@@ -850,6 +840,9 @@ def test_aessiv_decrypt_kat_rfc5297():
850840
def test_aessiv_encrypt_kat_openssl():
851841
"""
852842
Known-answer test using test vectors from OpenSSL.
843+
844+
This also tests calling AesSiv with a single associated data block, not
845+
provided as a list of blocks.
853846
"""
854847
aessiv = AesSiv(TEST_VECTOR_KEY_OPENSSL)
855848
siv, ciphertext = aessiv.encrypt(
@@ -865,6 +858,9 @@ def test_aessiv_encrypt_kat_openssl():
865858
def test_aessiv_decrypt_kat_openssl():
866859
"""
867860
Known-answer test using test vectors from OpenSSL.
861+
862+
This also tests calling AesSiv with a single associated data block, not
863+
provided as a list of blocks.
868864
"""
869865
aessiv = AesSiv(TEST_VECTOR_KEY_OPENSSL)
870866
plaintext = aessiv.decrypt(

wolfcrypt/ciphers.py

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,24 @@ def encrypt(self, associated_data, nonce, plaintext):
293293
"""
294294
Encrypt plaintext data using the nonce provided. The associated
295295
data is not encrypted but is included in the authentication tag.
296-
296+
297+
Associated data may be provided as single str or bytes, or as a
298+
list of str or bytes in case of multiple blocks.
299+
297300
Returns a tuple of the IV and ciphertext.
298301
"""
299-
associated_data = t2b(associated_data)
302+
# Prepare the associated data blocks. Make sure to hold on to the
303+
# returned references until the C function has been called in order
304+
# to prevent garbage collection of them until the function is done.
305+
associated_data, _refs = (
306+
AesSiv._prepare_associated_data(associated_data))
300307
nonce = t2b(nonce)
301308
plaintext = t2b(plaintext)
302309
siv = _ffi.new("byte[%d]" % AesSiv.block_size)
303310
ciphertext = _ffi.new("byte[%d]" % len(plaintext))
304-
ret = _lib.wc_AesSivEncrypt(self._key, len(self._key), associated_data, len(associated_data),
305-
nonce, len(nonce), plaintext, len(plaintext), siv, ciphertext)
311+
ret = _lib.wc_AesSivEncrypt_ex(self._key, len(self._key),
312+
associated_data, len(associated_data), nonce, len(nonce),
313+
plaintext, len(plaintext), siv, ciphertext)
306314
if ret < 0: # pragma: no cover
307315
raise WolfCryptError("AES-SIV encryption error (%d)" % ret)
308316
return _ffi.buffer(siv)[:], _ffi.buffer(ciphertext)[:]
@@ -312,22 +320,68 @@ def decrypt(self, associated_data, nonce, siv, ciphertext):
312320
Decrypt the ciphertext using the nonce and SIV provided.
313321
The integrity of the associated data is checked.
314322
323+
Associated data may be provided as single str or bytes, or as a
324+
list of str or bytes in case of multiple blocks.
325+
315326
Returns the decrypted plaintext.
316327
"""
317-
associated_data = t2b(associated_data)
328+
# Prepare the associated data blocks. Make sure to hold on to the
329+
# returned references until the C function has been called in order
330+
# to prevent garbage collection of them until the function is done.
331+
associated_data, _refs = (
332+
AesSiv._prepare_associated_data(associated_data))
318333
nonce = t2b(nonce)
319334
siv = t2b(siv)
320335
if len(siv) != AesSiv.block_size:
321336
raise ValueError("SIV must be %s in length, not %d" %
322337
(AesSiv.block_size, len(siv)))
323338
ciphertext = t2b(ciphertext)
324339
plaintext = _ffi.new("byte[%d]" % len(ciphertext))
325-
ref = _lib.wc_AesSivDecrypt(self._key, len(self._key), associated_data, len(associated_data),
326-
nonce, len(nonce), ciphertext, len(ciphertext), siv, plaintext)
340+
ref = _lib.wc_AesSivDecrypt_ex(self._key, len(self._key),
341+
associated_data, len(associated_data), nonce, len(nonce),
342+
ciphertext, len(ciphertext), siv, plaintext)
327343
if ref < 0:
328344
raise WolfCryptError("AES-SIV decryption error (%d)" % ref)
329345
return _ffi.buffer(plaintext)[:]
330346

347+
@staticmethod
348+
def _prepare_associated_data(associated_data):
349+
"""
350+
Prepare associated data for sending to C library.
351+
352+
Associated data may be provided as single str or bytes, or as a
353+
list of str or bytes in case of multiple blocks.
354+
355+
The result is a tuple of the list of cffi cdata pointers to
356+
AesSivAssoc structures, as well as the converted associated
357+
data blocks. The caller **must** hold on to these until the
358+
C function has been called, in order to make sure that the memory
359+
is not freed by the FFI garbage collector before the data is read.
360+
"""
361+
if (isinstance(associated_data, str) or isinstance(associated_data, bytes)):
362+
# A single block is provided.
363+
# Make sure we have bytes.
364+
associated_data = t2b(associated_data)
365+
result = _ffi.new("AesSivAssoc[1]")
366+
result[0].assoc = _ffi.from_buffer(associated_data)
367+
result[0].assocSz = len(associated_data)
368+
else:
369+
# It is assumed that a list is provided.
370+
num_blocks = len(associated_data)
371+
if (num_blocks > 126):
372+
raise WolfCryptError("AES-SIV does not support more than 126 blocks "
373+
"of associated data, got: %d" % num_blocks)
374+
# Make sure we have bytes.
375+
associated_data = [t2b(block) for block in associated_data]
376+
result = _ffi.new("AesSivAssoc[]", num_blocks)
377+
for index, block in enumerate(associated_data):
378+
result[index].assoc = _ffi.from_buffer(block)
379+
result[index].assocSz = len(block)
380+
# Return the converted associated data blocks so the caller can
381+
# hold on to them until the function has been called.
382+
return result, associated_data
383+
384+
331385
if _lib.AESGCM_STREAM_ENABLED:
332386
class AesGcmStream(object):
333387
"""

0 commit comments

Comments
 (0)