Fix AesSiv silently mangling associated data

AesSiv._prepare_associated_data only checked for str and bytes when
deciding whether the input was a single associated-data block, so
bytearray and memoryview fell through to the "list of blocks" branch.
Iterating those types yields integers, which t2b() then turned into
ASCII decimal byte-strings (b'16', b'17', ...), producing many bogus
blocks instead of one. The C SIV computation succeeded over wrong
associated data, so encryption/decryption silently produced an
incorrect tag rather than raising. This broke interoperability between
callers passing the same content as different buffer types.

Match the set of types accepted by t2b() by including bytearray and
memoryview in the isinstance check.

Add a parametrized test that reuses the OpenSSL KAT vectors with bytes,
bytearray, and memoryview wrappers; a round-trip test would not have
caught this since both sides mangle identically.

F-1981
pull/118/head
Andrew Hutchings 2026-05-11 11:47:56 +01:00
parent 4ec45a6511
commit 6c1c1b76bb
2 changed files with 30 additions and 1 deletions

View File

@ -938,6 +938,35 @@ def test_aessiv_decrypt_kat_openssl():
assert plaintext == TEST_VECTOR_PLAINTEXT_OPENSSL
@pytest.mark.skipif(not _lib.AES_SIV_ENABLED, reason="AES-SIV not enabled")
@pytest.mark.parametrize("wrap", [bytes, bytearray, memoryview],
ids=["bytes", "bytearray", "memoryview"])
def test_aessiv_associated_data_accepts_buffer_types(wrap):
"""
Single-block associated_data passed as bytes, bytearray, or memoryview
must all produce the same SIV/ciphertext as the OpenSSL KAT. A previous
bug treated bytearray/memoryview as a sequence of int blocks, producing
a different (incorrect) tag without raising.
"""
aessiv = AesSiv(TEST_VECTOR_KEY_OPENSSL)
associated_data = wrap(TEST_VECTOR_ASSOCIATED_DATA_OPENSSL)
siv, ciphertext = aessiv.encrypt(
associated_data,
TEST_VECTOR_NONCE_OPENSSL,
TEST_VECTOR_PLAINTEXT_OPENSSL
)
assert siv == TEST_VECTOR_SIV_OPENSSL
assert ciphertext == TEST_VECTOR_CIPHERTEXT_OPENSSL
plaintext = aessiv.decrypt(
wrap(TEST_VECTOR_ASSOCIATED_DATA_OPENSSL),
TEST_VECTOR_NONCE_OPENSSL,
TEST_VECTOR_SIV_OPENSSL,
TEST_VECTOR_CIPHERTEXT_OPENSSL
)
assert plaintext == TEST_VECTOR_PLAINTEXT_OPENSSL
if _lib.DES3_ENABLED:
def test_des3_rejects_mode_ctr():
key = b"\x01\x23\x45\x67\x89\xab\xcd\xef" * 3

View File

@ -363,7 +363,7 @@ if _lib.AES_SIV_ENABLED:
C function has been called, in order to make sure that the memory
is not freed by the FFI garbage collector before the data is read.
"""
if isinstance(associated_data, str) or isinstance(associated_data, bytes):
if isinstance(associated_data, (str, bytes, bytearray, memoryview)):
# A single block is provided.
# Make sure we have bytes.
associated_data = t2b(associated_data)