diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst
index 30bfbce52cf..b1a86c0f5c2 100644
--- a/doc/api_ref/ffi.rst
+++ b/doc/api_ref/ffi.rst
@@ -544,6 +544,8 @@ Symmetric Ciphers
.. cpp:function:: int botan_cipher_is_authenticated(botan_cipher_t cipher)
+.. cpp:function:: int botan_cipher_requires_entire_message(botan_cipher_t cipher)
+
.. cpp:function:: int botan_cipher_get_tag_length(botan_cipher_t cipher, size_t* tag_len)
Write the tag length of the cipher to ``tag_len``. This will be zero for non-authenticated
diff --git a/src/lib/ffi/ffi.cpp b/src/lib/ffi/ffi.cpp
index 4fa34d1b6a4..2fe79ce1c3d 100644
--- a/src/lib/ffi/ffi.cpp
+++ b/src/lib/ffi/ffi.cpp
@@ -221,6 +221,11 @@ uint32_t botan_ffi_api_version() {
}
int botan_ffi_supports_api(uint32_t api_version) {
+ // This is the API introduced in 3.4
+ if(api_version == 20240408) {
+ return BOTAN_FFI_SUCCESS;
+ }
+
// This is the API introduced in 3.2
if(api_version == 20231009) {
return BOTAN_FFI_SUCCESS;
diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h
index b437d5c69de..506507a2b79 100644
--- a/src/lib/ffi/ffi.h
+++ b/src/lib/ffi/ffi.h
@@ -557,6 +557,13 @@ BOTAN_FFI_EXPORT(2, 0) int botan_cipher_get_tag_length(botan_cipher_t cipher, si
*/
BOTAN_FFI_EXPORT(3, 3) int botan_cipher_is_authenticated(botan_cipher_t cipher);
+/**
+ * Returns 1 iff the cipher requires the entire message before any
+ * encryption or decryption can be performed. No output data will be produced
+ * in botan_cipher_update() until the final flag is set.
+ */
+BOTAN_FFI_EXPORT(3, 4) int botan_cipher_requires_entire_message(botan_cipher_t cipher);
+
/**
* Get the default nonce length of this cipher
*/
diff --git a/src/lib/ffi/ffi_cipher.cpp b/src/lib/ffi/ffi_cipher.cpp
index 3646613a79a..23239c0d82e 100644
--- a/src/lib/ffi/ffi_cipher.cpp
+++ b/src/lib/ffi/ffi_cipher.cpp
@@ -190,18 +190,19 @@ int botan_cipher_update(botan_cipher_t cipher_obj,
size_t taken = 0, written = 0;
while(input_size >= ud && output_size >= ud) {
- // FIXME we can use process here and avoid the copy
copy_mem(mbuf.data(), input, ud);
- cipher.update(mbuf);
+ const size_t bytes_produced = cipher.process(mbuf);
input_size -= ud;
- copy_mem(output, mbuf.data(), ud);
input += ud;
taken += ud;
- output_size -= ud;
- output += ud;
- written += ud;
+ if(bytes_produced > 0) {
+ copy_mem(output, mbuf.data(), bytes_produced);
+ output_size -= bytes_produced;
+ output += bytes_produced;
+ written += bytes_produced;
+ }
}
*output_written = written;
@@ -245,6 +246,10 @@ int botan_cipher_is_authenticated(botan_cipher_t cipher) {
return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return c.authenticated() ? 1 : 0; });
}
+int botan_cipher_requires_entire_message(botan_cipher_t cipher) {
+ return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return c.requires_entire_message() ? 1 : 0; });
+}
+
int botan_cipher_name(botan_cipher_t cipher, char* name, size_t* name_len) {
return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return write_str_output(name, name_len, c.name()); });
}
diff --git a/src/lib/ffi/info.txt b/src/lib/ffi/info.txt
index aac8028924a..19fdb9d538e 100644
--- a/src/lib/ffi/info.txt
+++ b/src/lib/ffi/info.txt
@@ -1,5 +1,5 @@
-FFI -> 20231009
+FFI -> 20240408
diff --git a/src/python/botan3.py b/src/python/botan3.py
index 54bebf1d6db..95c392de5f6 100755
--- a/src/python/botan3.py
+++ b/src/python/botan3.py
@@ -26,7 +26,7 @@
from binascii import hexlify
from datetime import datetime
-BOTAN_FFI_VERSION = 20230403
+BOTAN_FFI_VERSION = 20240408
#
# Base exception for all exceptions raised from this module
@@ -176,7 +176,10 @@ def ffi_api(fn, args, allowed_errors=None):
ffi_api(dll.botan_cipher_valid_nonce_length, [c_void_p, c_size_t])
ffi_api(dll.botan_cipher_get_tag_length, [c_void_p, POINTER(c_size_t)])
ffi_api(dll.botan_cipher_get_default_nonce_length, [c_void_p, POINTER(c_size_t)])
+ ffi_api(dll.botan_cipher_is_authenticated, [c_void_p])
+ ffi_api(dll.botan_cipher_requires_entire_message, [c_void_p])
ffi_api(dll.botan_cipher_get_update_granularity, [c_void_p, POINTER(c_size_t)])
+ ffi_api(dll.botan_cipher_get_ideal_update_granularity, [c_void_p, POINTER(c_size_t)])
ffi_api(dll.botan_cipher_query_keylen, [c_void_p, POINTER(c_size_t), POINTER(c_size_t)])
ffi_api(dll.botan_cipher_get_keyspec, [c_void_p, POINTER(c_size_t), POINTER(c_size_t), POINTER(c_size_t)])
ffi_api(dll.botan_cipher_set_key, [c_void_p, c_char_p, c_size_t])
@@ -853,6 +856,11 @@ def update_granularity(self):
_DLL.botan_cipher_get_update_granularity(self.__obj, byref(l))
return l.value
+ def ideal_update_granularity(self):
+ l = c_size_t(0)
+ _DLL.botan_cipher_get_ideal_update_granularity(self.__obj, byref(l))
+ return l.value
+
def key_length(self):
kmin = c_size_t(0)
kmax = c_size_t(0)
@@ -875,7 +883,8 @@ def tag_length(self):
return l.value
def is_authenticated(self):
- return self.tag_length() > 0
+ rc = _DLL.botan_cipher_is_authenticated(self.__obj)
+ return rc == 1
def valid_nonce_length(self, nonce_len):
rc = _DLL.botan_cipher_valid_nonce_length(self.__obj, nonce_len)
diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py
index 3fd54090598..a11a71bf626 100644
--- a/src/scripts/test_python.py
+++ b/src/scripts/test_python.py
@@ -212,14 +212,31 @@ def test_hash(self):
def test_cipher(self):
for mode in ['AES-128/CTR-BE', 'Serpent/GCM', 'ChaCha20Poly1305', 'AES-128/CBC/PKCS7']:
- enc = botan.SymmetricCipher(mode, encrypt=True)
+ try:
+ enc = botan.SymmetricCipher(mode, encrypt=True)
+ except botan.BotanException as e:
+ raise RuntimeError("Failed to create encrypting cipher for " + mode) from e
if mode == 'AES-128/CTR-BE':
self.assertEqual(enc.algo_name(), 'CTR-BE(AES-128)')
+ self.assertFalse(enc.is_authenticated())
+ self.assertEqual(enc.update_granularity(), 1)
+ self.assertGreater(enc.ideal_update_granularity(), 1)
elif mode == 'Serpent/GCM':
self.assertEqual(enc.algo_name(), 'Serpent/GCM(16)')
- else:
- self.assertEqual(enc.algo_name(), mode)
+ self.assertTrue(enc.is_authenticated())
+ self.assertEqual(enc.update_granularity(), 16)
+ self.assertGreater(enc.ideal_update_granularity(), 16)
+ elif mode == 'ChaCha20Poly1305':
+ self.assertEqual(enc.algo_name(), 'ChaCha20Poly1305')
+ self.assertTrue(enc.is_authenticated())
+ self.assertEqual(enc.update_granularity(), 1)
+ self.assertGreater(enc.ideal_update_granularity(), 1)
+ elif mode == 'AES-128/CBC/PKCS7':
+ self.assertEqual(enc.algo_name(), 'AES-128/CBC/PKCS7')
+ self.assertFalse(enc.is_authenticated())
+ self.assertEqual(enc.update_granularity(), 16)
+ self.assertGreater(enc.ideal_update_granularity(), 16)
(kmin, kmax) = enc.key_length()
@@ -238,7 +255,10 @@ def test_cipher(self):
ct = enc.finish(pt)
- dec = botan.SymmetricCipher(mode, encrypt=False)
+ try:
+ dec = botan.SymmetricCipher(mode, encrypt=False)
+ except botan.BotanException as e:
+ raise RuntimeError("Failed to create decrypting cipher for " + mode) from e
dec.set_key(key)
dec.start(iv)
decrypted = dec.finish(ct)
diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp
index fcc70120d35..0ed73ee8b05 100644
--- a/src/tests/test_ffi.cpp
+++ b/src/tests/test_ffi.cpp
@@ -15,6 +15,7 @@
#if defined(BOTAN_HAS_FFI)
#include
#include
+ #include
#include
#include
#endif
@@ -973,6 +974,123 @@ class FFI_GCM_Test final : public FFI_Test {
}
};
+class FFI_ChaCha20Poly1305_Test final : public FFI_Test {
+ public:
+ std::string name() const override { return "FFI ChaCha20Poly1305"; }
+
+ void ffi_test(Test::Result& result, botan_rng_t /*unused*/) override {
+ botan_cipher_t cipher_encrypt, cipher_decrypt;
+
+ if(TEST_FFI_INIT(botan_cipher_init, (&cipher_encrypt, "ChaCha20Poly1305", BOTAN_CIPHER_INIT_FLAG_ENCRYPT))) {
+ std::array namebuf;
+ size_t name_len = 15;
+ TEST_FFI_FAIL("output buffer too short", botan_cipher_name, (cipher_encrypt, namebuf.data(), &name_len));
+ result.test_eq("name len", name_len, 17);
+
+ name_len = namebuf.size();
+ if(TEST_FFI_OK(botan_cipher_name, (cipher_encrypt, namebuf.data(), &name_len))) {
+ result.test_eq("name len", name_len, 17);
+ result.test_eq("name", std::string(namebuf.data()), "ChaCha20Poly1305");
+ }
+
+ size_t min_keylen = 0;
+ size_t max_keylen = 0;
+ size_t nonce_len = 0;
+ size_t tag_len = 0;
+
+ TEST_FFI_OK(botan_cipher_query_keylen, (cipher_encrypt, &min_keylen, &max_keylen));
+ result.test_int_eq(min_keylen, 32, "Min key length");
+ result.test_int_eq(max_keylen, 32, "Max key length");
+
+ TEST_FFI_OK(botan_cipher_get_default_nonce_length, (cipher_encrypt, &nonce_len));
+ result.test_int_eq(nonce_len, 12, "Expected default ChaCha20Poly1305 nonce length");
+
+ TEST_FFI_OK(botan_cipher_get_tag_length, (cipher_encrypt, &tag_len));
+ result.test_int_eq(tag_len, 16, "Expected Chacha20Poly1305 tag length");
+
+ TEST_FFI_RC(1, botan_cipher_is_authenticated, (cipher_encrypt));
+
+ // From RFC 7539
+ const std::vector plaintext = Botan::hex_decode(
+ "4C616469657320616E642047656E746C656D656E206F662074686520636C617373206F66202739393A204966204920636F756C64206F6666657220796F75206F6E6C79206F6E652074697020666F7220746865206675747572652C2073756E73637265656E20776F756C642062652069742E");
+ const std::vector symkey =
+ Botan::hex_decode("808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9F");
+ const std::vector nonce = Botan::hex_decode("070000004041424344454647");
+ const std::vector exp_ciphertext = Botan::hex_decode(
+ "D31A8D34648E60DB7B86AFBC53EF7EC2A4ADED51296E08FEA9E2B5A736EE62D63DBEA45E8CA9671282FAFB69DA92728B1A71DE0A9E060B2905D6A5B67ECD3B3692DDBD7F2D778B8C9803AEE328091B58FAB324E4FAD675945585808B4831D7BC3FF4DEF08E4B7A9DE576D26586CEC64B61161AE10B594F09E26A7E902ECBD0600691");
+ const std::vector aad = Botan::hex_decode("50515253C0C1C2C3C4C5C6C7");
+
+ std::vector ciphertext(tag_len + plaintext.size());
+
+ size_t output_written = 0;
+ size_t input_consumed = 0;
+
+ // Test that after clear or final the object can be reused
+ for(size_t r = 0; r != 2; ++r) {
+ TEST_FFI_OK(botan_cipher_set_key, (cipher_encrypt, symkey.data(), symkey.size()));
+
+ // First use a nonce of the AAD, and ensure reset works
+ TEST_FFI_OK(botan_cipher_start, (cipher_encrypt, aad.data(), aad.size()));
+ TEST_FFI_OK(botan_cipher_reset, (cipher_encrypt));
+
+ TEST_FFI_OK(botan_cipher_start, (cipher_encrypt, nonce.data(), nonce.size()));
+ TEST_FFI_OK(botan_cipher_update,
+ (cipher_encrypt,
+ 0,
+ ciphertext.data(),
+ ciphertext.size(),
+ &output_written,
+ plaintext.data(),
+ plaintext.size(),
+ &input_consumed));
+ TEST_FFI_OK(botan_cipher_clear, (cipher_encrypt));
+
+ TEST_FFI_OK(botan_cipher_set_key, (cipher_encrypt, symkey.data(), symkey.size()));
+ TEST_FFI_OK(botan_cipher_set_associated_data, (cipher_encrypt, aad.data(), aad.size()));
+ TEST_FFI_OK(botan_cipher_start, (cipher_encrypt, nonce.data(), nonce.size()));
+ TEST_FFI_OK(botan_cipher_update,
+ (cipher_encrypt,
+ BOTAN_CIPHER_UPDATE_FLAG_FINAL,
+ ciphertext.data(),
+ ciphertext.size(),
+ &output_written,
+ plaintext.data(),
+ plaintext.size(),
+ &input_consumed));
+
+ ciphertext.resize(output_written);
+ result.test_eq("AES/GCM ciphertext", ciphertext, exp_ciphertext);
+
+ if(TEST_FFI_OK(botan_cipher_init,
+ (&cipher_decrypt, "ChaCha20Poly1305", BOTAN_CIPHER_INIT_FLAG_DECRYPT))) {
+ std::vector decrypted(plaintext.size());
+
+ TEST_FFI_OK(botan_cipher_set_key, (cipher_decrypt, symkey.data(), symkey.size()));
+ TEST_FFI_OK(botan_cipher_set_associated_data, (cipher_decrypt, aad.data(), aad.size()));
+ TEST_FFI_OK(botan_cipher_start, (cipher_decrypt, nonce.data(), nonce.size()));
+ TEST_FFI_OK(botan_cipher_update,
+ (cipher_decrypt,
+ BOTAN_CIPHER_UPDATE_FLAG_FINAL,
+ decrypted.data(),
+ decrypted.size(),
+ &output_written,
+ ciphertext.data(),
+ ciphertext.size(),
+ &input_consumed));
+
+ result.test_int_eq(input_consumed, ciphertext.size(), "All input consumed");
+ result.test_int_eq(output_written, decrypted.size(), "Expected output size produced");
+ result.test_eq("AES/GCM plaintext", decrypted, plaintext);
+
+ TEST_FFI_OK(botan_cipher_destroy, (cipher_decrypt));
+ }
+ }
+
+ TEST_FFI_OK(botan_cipher_destroy, (cipher_encrypt));
+ }
+ }
+};
+
class FFI_EAX_Test final : public FFI_Test {
public:
std::string name() const override { return "FFI EAX"; }
@@ -1078,6 +1196,218 @@ class FFI_EAX_Test final : public FFI_Test {
}
};
+class FFI_AEAD_Test final : public FFI_Test {
+ public:
+ std::string name() const override { return "FFI AEAD"; }
+
+ void ffi_test(Test::Result& merged_result, botan_rng_t rng) override {
+ botan_cipher_t cipher_encrypt, cipher_decrypt;
+
+ std::array aeads = {
+ "AES-128/GCM", "ChaCha20Poly1305", "AES-128/EAX", "AES-256/SIV", "AES-128/CCM"};
+
+ for(const std::string& aead : aeads) {
+ Test::Result result(Botan::fmt("AEAD {}", aead));
+
+ if(!TEST_FFI_INIT(botan_cipher_init, (&cipher_encrypt, aead.c_str(), BOTAN_CIPHER_INIT_FLAG_ENCRYPT))) {
+ continue;
+ }
+
+ if(!botan_cipher_is_authenticated(cipher_encrypt)) {
+ result.test_failure("Cipher " + aead + " claims is not authenticated");
+ botan_cipher_destroy(cipher_encrypt);
+ continue;
+ }
+
+ size_t min_keylen = 0;
+ size_t max_keylen = 0;
+ size_t ideal_granularity = 0;
+ size_t noncelen = 0;
+ size_t taglen = 0;
+ constexpr size_t pt_multiplier = 5;
+ TEST_FFI_OK(botan_cipher_query_keylen, (cipher_encrypt, &min_keylen, &max_keylen));
+ TEST_FFI_OK(botan_cipher_get_ideal_update_granularity, (cipher_encrypt, &ideal_granularity));
+ TEST_FFI_OK(botan_cipher_get_default_nonce_length, (cipher_encrypt, &noncelen));
+ TEST_FFI_OK(botan_cipher_get_tag_length, (cipher_encrypt, &taglen));
+
+ std::vector key(max_keylen);
+ TEST_FFI_OK(botan_rng_get, (rng, key.data(), key.size()));
+ TEST_FFI_OK(botan_cipher_set_key, (cipher_encrypt, key.data(), key.size()));
+
+ std::vector nonce(noncelen);
+ TEST_FFI_OK(botan_rng_get, (rng, nonce.data(), nonce.size()));
+ TEST_FFI_OK(botan_cipher_start, (cipher_encrypt, nonce.data(), nonce.size()));
+
+ std::vector plaintext(ideal_granularity * pt_multiplier);
+ std::vector ciphertext(ideal_granularity * pt_multiplier + taglen);
+ TEST_FFI_OK(botan_rng_get, (rng, plaintext.data(), plaintext.size()));
+
+ std::vector dummy_buffer(256);
+ TEST_FFI_OK(botan_rng_get, (rng, dummy_buffer.data(), dummy_buffer.size()));
+ std::vector dummy_buffer_reference = dummy_buffer;
+
+ const bool requires_entire_message = botan_cipher_requires_entire_message(cipher_encrypt);
+ result.test_eq(
+ "requires entire message", requires_entire_message, (aead == "AES-256/SIV" || aead == "AES-128/CCM"));
+
+ std::span pt_slicer(plaintext);
+ std::span ct_stuffer(ciphertext);
+
+ // Process data that is explicitly a multiple of the ideal
+ // granularity and therefore should be aligned with the cipher's
+ // internal block size.
+ for(size_t i = 0; i < pt_multiplier; ++i) {
+ size_t output_written = 0;
+ size_t input_consumed = 0;
+
+ auto pt_chunk = pt_slicer.first(ideal_granularity);
+
+ // The existing implementation won't consume any bytes from the
+ // input if there is no space in the output buffer. Even when
+ // the cipher is a mode that won't produce any output until the
+ // entire message is processed. Hence, give it some dummy buffer.
+ auto ct_chunk = (requires_entire_message) ? std::span(dummy_buffer).first(ideal_granularity)
+ : ct_stuffer.first(ideal_granularity);
+
+ TEST_FFI_OK(botan_cipher_update,
+ (cipher_encrypt,
+ 0 /* don't finalize */,
+ ct_chunk.data(),
+ ct_chunk.size(),
+ &output_written,
+ pt_chunk.data(),
+ pt_chunk.size(),
+ &input_consumed));
+
+ result.test_gt("some input consumed", input_consumed, 0);
+ result.test_lte("at most, all input consumed", input_consumed, pt_chunk.size());
+ pt_slicer = pt_slicer.subspan(input_consumed);
+
+ if(requires_entire_message) {
+ result.test_eq("no output produced", output_written, 0);
+ } else {
+ result.test_eq("all bytes produced", output_written, input_consumed);
+ ct_stuffer = ct_stuffer.subspan(output_written);
+ }
+ }
+
+ // Trying to pull a part of the authentication tag should fail,
+ // as we must consume the entire tag in a single invocation to
+ // botan_cipher_update().
+ size_t final_output_written = 42;
+ size_t final_input_consumed = 1337;
+ TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_INPUT,
+ botan_cipher_update,
+ (cipher_encrypt,
+ BOTAN_CIPHER_UPDATE_FLAG_FINAL,
+ dummy_buffer.data(),
+ 3, /* not enough to hold any reasonable auth'n tag */
+ &final_output_written,
+ pt_slicer.data(), // remaining bytes (typically 0)
+ pt_slicer.size(),
+ &final_input_consumed));
+
+ const size_t expected_final_size = requires_entire_message ? ciphertext.size() : taglen + pt_slicer.size();
+
+ result.test_eq("remaining bytes consumed in bogus final", final_input_consumed, pt_slicer.size());
+ result.test_eq("required buffer size is written in bogus final", final_output_written, expected_final_size);
+
+ auto final_ct_chunk = ct_stuffer.first(expected_final_size);
+
+ TEST_FFI_OK(botan_cipher_update,
+ (cipher_encrypt,
+ 0 /* explicitly not setting final flag, to fall into the 'input-size == 0' case */,
+ final_ct_chunk.data(),
+ final_ct_chunk.size(),
+ &final_output_written,
+ nullptr, // no more input
+ 0,
+ &final_input_consumed));
+
+ result.test_eq("no bytes consumed in final", final_input_consumed, 0);
+ result.test_eq("final bytes written", final_output_written, expected_final_size);
+ result.test_eq("dummy buffer unchanged", dummy_buffer, dummy_buffer_reference);
+
+ TEST_FFI_OK(botan_cipher_destroy, (cipher_encrypt));
+
+ // ----------------------------------------------------------------
+
+ TEST_FFI_INIT(botan_cipher_init, (&cipher_decrypt, aead.c_str(), BOTAN_CIPHER_INIT_FLAG_DECRYPT));
+ TEST_FFI_OK(botan_cipher_set_key, (cipher_decrypt, key.data(), key.size()));
+ TEST_FFI_OK(botan_cipher_start, (cipher_decrypt, nonce.data(), nonce.size()));
+
+ std::vector decrypted(plaintext.size());
+
+ std::span ct_slicer(ciphertext);
+ std::span pt_stuffer(decrypted);
+
+ // Process data that is explicitly a multiple of the ideal
+ // granularity and therefore should be aligned with the cipher's
+ // internal block size.
+ for(size_t i = 0; i < pt_multiplier; ++i) {
+ size_t output_written = 42;
+ size_t input_consumed = 1337;
+
+ auto ct_chunk = ct_slicer.first(ideal_granularity);
+
+ // The existing implementation won't consume any bytes from the
+ // input if there is no space in the output buffer. Even when
+ // the cipher is a mode that won't produce any output until the
+ // entire message is processed. Hence, give it some dummy buffer.
+ auto pt_chunk = (requires_entire_message) ? std::span(dummy_buffer).first(ideal_granularity)
+ : pt_stuffer.first(ideal_granularity);
+
+ TEST_FFI_OK(botan_cipher_update,
+ (cipher_decrypt,
+ 0 /* don't finalize */,
+ pt_chunk.data(),
+ pt_chunk.size(),
+ &output_written,
+ ct_chunk.data(),
+ ct_chunk.size(),
+ &input_consumed));
+
+ result.test_gt("some input consumed", input_consumed, 0);
+ result.test_lte("at most, all input consumed", input_consumed, ct_chunk.size());
+ ct_slicer = ct_slicer.subspan(input_consumed);
+
+ if(requires_entire_message) {
+ result.test_eq("no output produced", output_written, 0);
+ } else {
+ result.test_eq("all bytes produced", output_written, input_consumed);
+ pt_stuffer = pt_stuffer.subspan(output_written);
+ }
+ }
+
+ const size_t expected_final_size_dec = requires_entire_message ? plaintext.size() : pt_stuffer.size();
+ auto pt_chunk = pt_stuffer.first(expected_final_size_dec);
+
+ size_t final_output_written_dec = 42;
+ size_t final_input_consumed_dec = 1337;
+
+ TEST_FFI_OK(botan_cipher_update,
+ (cipher_decrypt,
+ BOTAN_CIPHER_UPDATE_FLAG_FINAL,
+ pt_chunk.data(),
+ pt_chunk.size(),
+ &final_output_written_dec,
+ ct_slicer.data(), // remaining bytes (typically 0)
+ ct_slicer.size(),
+ &final_input_consumed_dec));
+
+ result.test_eq("remaining bytes consumed in final (decrypt)", final_input_consumed_dec, ct_slicer.size());
+ result.test_eq("bytes written in final (decrypt)", final_output_written_dec, expected_final_size_dec);
+ result.test_eq("dummy buffer unchanged", dummy_buffer, dummy_buffer_reference);
+
+ result.test_eq("decrypted plaintext", decrypted, plaintext);
+
+ TEST_FFI_OK(botan_cipher_destroy, (cipher_decrypt));
+
+ merged_result.merge(result, true /* ignore names */);
+ }
+ }
+};
+
class FFI_StreamCipher_Test final : public FFI_Test {
public:
std::string name() const override { return "FFI stream ciphers"; }
@@ -3142,7 +3472,9 @@ BOTAN_REGISTER_TEST("ffi", "ffi_ecdsa_certificate", FFI_ECDSA_Certificate_Test);
BOTAN_REGISTER_TEST("ffi", "ffi_pkcs_hashid", FFI_PKCS_Hashid_Test);
BOTAN_REGISTER_TEST("ffi", "ffi_cbc_cipher", FFI_CBC_Cipher_Test);
BOTAN_REGISTER_TEST("ffi", "ffi_gcm", FFI_GCM_Test);
+BOTAN_REGISTER_TEST("ffi", "ffi_chacha", FFI_ChaCha20Poly1305_Test);
BOTAN_REGISTER_TEST("ffi", "ffi_eax", FFI_EAX_Test);
+BOTAN_REGISTER_TEST("ffi", "ffi_aead", FFI_AEAD_Test);
BOTAN_REGISTER_TEST("ffi", "ffi_streamcipher", FFI_StreamCipher_Test);
BOTAN_REGISTER_TEST("ffi", "ffi_hashfunction", FFI_HashFunction_Test);
BOTAN_REGISTER_TEST("ffi", "ffi_mac", FFI_MAC_Test);